Factor out a single parseNumber utility function (#1839)

This commit is contained in:
Rangi
2025-09-22 15:15:24 -04:00
committed by GitHub
parent c8d22d8744
commit 634fd853d1
18 changed files with 304 additions and 337 deletions

View File

@@ -8,6 +8,7 @@
#include <stddef.h> #include <stddef.h>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>

View File

@@ -4,8 +4,9 @@
#define RGBDS_UTIL_HPP #define RGBDS_UTIL_HPP
#include <algorithm> #include <algorithm>
#include <ctype.h> #include <ctype.h> // toupper
#include <numeric> #include <numeric>
#include <optional>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <string> #include <string>
@@ -13,12 +14,21 @@
#include "helpers.hpp" #include "helpers.hpp"
enum NumberBase {
BASE_AUTO = 0,
BASE_2 = 2,
BASE_8 = 8,
BASE_10 = 10,
BASE_16 = 16,
};
bool isNewline(int c); bool isNewline(int c);
bool isBlankSpace(int c); bool isBlankSpace(int c);
bool isWhitespace(int c); bool isWhitespace(int c);
bool isPrintable(int c); bool isPrintable(int c);
bool isLetter(int c); bool isLetter(int c);
bool isDigit(int c); bool isDigit(int c);
bool isBinDigit(int c);
bool isOctDigit(int c); bool isOctDigit(int c);
bool isHexDigit(int c); bool isHexDigit(int c);
bool isAlphanumeric(int c); bool isAlphanumeric(int c);
@@ -27,6 +37,8 @@ bool startsIdentifier(int c);
bool continuesIdentifier(int c); bool continuesIdentifier(int c);
uint8_t parseHexDigit(int c); uint8_t parseHexDigit(int c);
std::optional<uint64_t> parseNumber(char const *&str, NumberBase base = BASE_AUTO);
std::optional<uint64_t> parseWholeNumber(char const *str, NumberBase base = BASE_AUTO);
char const *printChar(int c); char const *printChar(int c);

View File

@@ -3,6 +3,8 @@
#ifndef RGBDS_VERBOSITY_HPP #ifndef RGBDS_VERBOSITY_HPP
#define RGBDS_VERBOSITY_HPP #define RGBDS_VERBOSITY_HPP
#include <stdio.h>
#include "style.hpp" #include "style.hpp"
// This macro does not evaluate its arguments unless the condition is true. // This macro does not evaluate its arguments unless the condition is true.

View File

@@ -11,24 +11,21 @@
#include <string.h> #include <string.h>
#include <string> #include <string>
#include "util.hpp" // isDigit #include "util.hpp" // parseNumber
#include "asm/main.hpp" // options #include "asm/main.hpp" // options
#include "asm/warning.hpp" #include "asm/warning.hpp"
static size_t parseNumber(char const *spec, size_t &value) {
size_t i = 0;
value = 0;
for (; isDigit(spec[i]); ++i) {
value = value * 10 + (spec[i] - '0');
}
return i;
}
size_t FormatSpec::parseSpec(char const *spec) { size_t FormatSpec::parseSpec(char const *spec) {
size_t i = 0; size_t i = 0;
auto parseSpecNumber = [&spec, &i]() {
char const *end = &spec[i];
size_t number = parseNumber(end, BASE_10).value_or(0);
i += end - &spec[i];
return number;
};
// <sign> // <sign>
if (char c = spec[i]; c == ' ' || c == '+') { if (char c = spec[i]; c == ' ' || c == '+') {
++i; ++i;
@@ -51,19 +48,19 @@ size_t FormatSpec::parseSpec(char const *spec) {
} }
// <width> // <width>
if (isDigit(spec[i])) { if (isDigit(spec[i])) {
i += parseNumber(&spec[i], width); width = parseSpecNumber();
} }
// <frac> // <frac>
if (spec[i] == '.') { if (spec[i] == '.') {
++i; ++i;
hasFrac = true; hasFrac = true;
i += parseNumber(&spec[i], fracWidth); fracWidth = parseSpecNumber();
} }
// <prec> // <prec>
if (spec[i] == 'q') { if (spec[i] == 'q') {
++i; ++i;
hasPrec = true; hasPrec = true;
i += parseNumber(&spec[i], precision); precision = parseSpecNumber();
} }
// <type> // <type>
switch (char c = spec[i]; c) { switch (char c = spec[i]; c) {

View File

@@ -1009,8 +1009,8 @@ static bool isValidDigit(char c) {
return isAlphanumeric(c) || c == '.' || c == '#' || c == '@'; return isAlphanumeric(c) || c == '.' || c == '#' || c == '@';
} }
static bool isBinDigit(int c) { static bool isCustomBinDigit(int c) {
return c == '0' || c == '1' || c == options.binDigits[0] || c == options.binDigits[1]; return isBinDigit(c) || c == options.binDigits[0] || c == options.binDigits[1];
} }
static bool checkDigitErrors(char const *digits, size_t n, char const *type) { static bool checkDigitErrors(char const *digits, size_t n, char const *type) {
@@ -1078,7 +1078,7 @@ static uint32_t readBinaryNumber(char const *prefix) {
if (value > (UINT32_MAX - bit) / 2) { if (value > (UINT32_MAX - bit) / 2) {
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large"); warning(WARNING_LARGE_CONSTANT, "Integer constant is too large");
// Discard any additional digits // Discard any additional digits
skipChars([](int d) { return isBinDigit(d) || d == '_'; }); skipChars([](int d) { return isCustomBinDigit(d) || d == '_'; });
return 0; return 0;
} }
value = value * 2 + bit; value = value * 2 + bit;
@@ -1836,7 +1836,7 @@ static Token yylex_NORMAL() {
case '%': // Either %=, MOD, or a binary constant case '%': // Either %=, MOD, or a binary constant
c = peek(); c = peek();
if (isBinDigit(c) || c == '_') { if (isCustomBinDigit(c) || c == '_') {
return Token(T_(NUMBER), readBinaryNumber("'%'")); return Token(T_(NUMBER), readBinaryNumber("'%'"));
} }
return oneOrTwo('=', T_(POP_MODEQ), T_(OP_MOD)); return oneOrTwo('=', T_(POP_MODEQ), T_(OP_MOD));

View File

@@ -6,6 +6,7 @@
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <memory> #include <memory>
#include <optional>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@@ -299,6 +300,8 @@ int main(int argc, char *argv[]) {
// https://reproducible-builds.org/docs/source-date-epoch/ // https://reproducible-builds.org/docs/source-date-epoch/
time_t now = time(nullptr); time_t now = time(nullptr);
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) { if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
// Use `strtoul`, not `parseWholeNumber`, because SOURCE_DATE_EPOCH does
// not conventionally support our custom base prefixes
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0)); now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
} }
sym_Init(now); sym_Init(now);
@@ -378,51 +381,41 @@ int main(int argc, char *argv[]) {
fstk_AddPreIncludeFile(musl_optarg); fstk_AddPreIncludeFile(musl_optarg);
break; break;
case 'p': { case 'p':
char *endptr; if (std::optional<uint64_t> padByte = parseWholeNumber(musl_optarg); !padByte) {
unsigned long padByte = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') {
fatal("Invalid argument for option '-p'"); fatal("Invalid argument for option '-p'");
} } else if (*padByte > 0xFF) {
if (padByte > 0xFF) {
fatal("Argument for option '-p' must be between 0 and 0xFF"); fatal("Argument for option '-p' must be between 0 and 0xFF");
} else {
opt_P(*padByte);
} }
opt_P(padByte);
break; break;
}
case 'Q': { case 'Q': {
char const *precisionArg = musl_optarg; char const *precisionArg = musl_optarg;
if (precisionArg[0] == '.') { if (precisionArg[0] == '.') {
++precisionArg; ++precisionArg;
} }
char *endptr;
unsigned long precision = strtoul(precisionArg, &endptr, 0);
if (precisionArg[0] == '\0' || *endptr != '\0') { if (std::optional<uint64_t> precision = parseWholeNumber(precisionArg); !precision) {
fatal("Invalid argument for option '-Q'"); fatal("Invalid argument for option '-Q'");
} } else if (*precision < 1 || *precision > 31) {
if (precision < 1 || precision > 31) {
fatal("Argument for option '-Q' must be between 1 and 31"); fatal("Argument for option '-Q' must be between 1 and 31");
} else {
opt_Q(*precision);
} }
opt_Q(precision);
break; break;
} }
case 'r': { case 'r':
char *endptr; if (std::optional<uint64_t> maxDepth = parseWholeNumber(musl_optarg); !maxDepth) {
options.maxRecursionDepth = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') {
fatal("Invalid argument for option '-r'"); fatal("Invalid argument for option '-r'");
} else if (errno == ERANGE) {
fatal("Argument for option '-r' is out of range");
} else {
options.maxRecursionDepth = *maxDepth;
} }
break; break;
}
case 's': { case 's': {
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>" // Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
@@ -459,21 +452,15 @@ int main(int argc, char *argv[]) {
warnings.state.warningsEnabled = false; warnings.state.warningsEnabled = false;
break; break;
case 'X': { case 'X':
char *endptr; if (std::optional<uint64_t> maxErrors = parseWholeNumber(musl_optarg); !maxErrors) {
uint64_t maxErrors = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') {
fatal("Invalid argument for option '-X'"); fatal("Invalid argument for option '-X'");
} } else if (*maxErrors > UINT64_MAX) {
if (maxErrors > UINT64_MAX) {
fatal("Argument for option '-X' must be between 0 and %" PRIu64, UINT64_MAX); fatal("Argument for option '-X' must be between 0 and %" PRIu64, UINT64_MAX);
} else {
options.maxErrors = *maxErrors;
} }
options.maxErrors = maxErrors;
break; break;
}
case 0: // Long-only options case 0: // Long-only options
switch (longOpt) { switch (longOpt) {

View File

@@ -2,6 +2,7 @@
#include <errno.h> #include <errno.h>
#include <iterator> // std::size #include <iterator> // std::size
#include <optional>
#include <stack> #include <stack>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@@ -9,8 +10,7 @@
#include <string.h> #include <string.h>
#include "diagnostics.hpp" #include "diagnostics.hpp"
#include "helpers.hpp" // assume #include "util.hpp"
#include "util.hpp" // isBlankSpace
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
#include "asm/lexer.hpp" #include "asm/lexer.hpp"
@@ -81,50 +81,38 @@ void opt_Parse(char const *s) {
} }
break; break;
case 'p': { case 'p':
char *endptr; if (std::optional<uint64_t> padByte = parseWholeNumber(s); !padByte) {
unsigned long padByte = strtoul(s, &endptr, 0);
if (s[0] == '\0' || *endptr != '\0') {
error("Invalid argument for option 'p'"); error("Invalid argument for option 'p'");
} else if (padByte > 0xFF) { } else if (*padByte > 0xFF) {
error("Argument for option 'p' must be between 0 and 0xFF"); error("Argument for option 'p' must be between 0 and 0xFF");
} else { } else {
opt_P(padByte); opt_P(*padByte);
} }
break; break;
}
case 'Q': { case 'Q':
if (s[0] == '.') { if (s[0] == '.') {
++s; // Skip leading '.' ++s; // Skip leading '.'
} }
char *endptr; if (std::optional<uint64_t> precision = parseWholeNumber(s); !precision) {
unsigned long precision = strtoul(s, &endptr, 0);
if (s[0] == '\0' || *endptr != '\0') {
error("Invalid argument for option 'Q'"); error("Invalid argument for option 'Q'");
} else if (precision < 1 || precision > 31) { } else if (*precision < 1 || *precision > 31) {
error("Argument for option 'Q' must be between 1 and 31"); error("Argument for option 'Q' must be between 1 and 31");
} else { } else {
opt_Q(precision); opt_Q(*precision);
} }
break; break;
}
case 'r': { case 'r':
char *endptr; if (std::optional<uint64_t> maxRecursionDepth = parseWholeNumber(s); !maxRecursionDepth) {
unsigned long maxRecursionDepth = strtoul(s, &endptr, 0);
if (s[0] == '\0' || *endptr != '\0') {
error("Invalid argument for option 'r'"); error("Invalid argument for option 'r'");
} else if (errno == ERANGE) { } else if (errno == ERANGE) {
error("Argument for option 'r' is out of range"); error("Argument for option 'r' is out of range");
} else { } else {
opt_R(maxRecursionDepth); opt_R(*maxRecursionDepth);
} }
break; break;
}
case 'W': case 'W':
if (strlen(s) > 0) { if (strlen(s) > 0) {

View File

@@ -2,9 +2,11 @@
#include "backtrace.hpp" #include "backtrace.hpp"
#include <stdlib.h> // strtoul #include <optional>
#include <stdint.h>
#include "platform.hpp" // strcasecmp #include "platform.hpp" // strcasecmp
#include "util.hpp" // parseWholeNumber
Tracing tracing; Tracing tracing;
@@ -22,8 +24,10 @@ bool trace_ParseTraceDepth(char const *arg) {
tracing.loud = false; tracing.loud = false;
return true; return true;
} else { } else {
char *endptr; std::optional<uint64_t> depth = parseWholeNumber(arg);
tracing.depth = strtoul(arg, &endptr, 0); if (depth) {
return arg[0] != '\0' && *endptr == '\0'; tracing.depth = *depth;
}
return depth.has_value();
} }
} }

View File

@@ -63,25 +63,9 @@ std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::str
return {state, std::nullopt}; return {state, std::nullopt};
} }
// Is the rest of the string a decimal number? // If the rest of the string is a decimal number, it's the parameter value
// We want to avoid `strtoul`'s whitespace and sign handling, so we parse manually
char const *ptr = flag.c_str() + equals + 1; char const *ptr = flag.c_str() + equals + 1;
uint32_t param = 0; uint64_t param = parseNumber(ptr, BASE_10).value_or(0);
bool overflowed = false;
for (; isDigit(*ptr); ++ptr) {
if (overflowed) {
continue;
}
uint32_t c = *ptr - '0';
if (param > (UINT32_MAX - c) / 10) {
overflowed = true;
param = UINT32_MAX;
continue;
}
param = param * 10 + c;
}
// If we reached the end of the string, truncate it at the '=' // If we reached the end of the string, truncate it at the '='
if (*ptr == '\0') { if (*ptr == '\0') {
@@ -92,5 +76,5 @@ std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::str
} }
} }
return {state, param}; return {state, param > UINT32_MAX ? UINT32_MAX : param};
} }

View File

@@ -3,7 +3,9 @@
#include "fix/main.hpp" #include "fix/main.hpp"
#include <errno.h> #include <errno.h>
#include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <optional>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@@ -16,6 +18,7 @@
#include "platform.hpp" #include "platform.hpp"
#include "style.hpp" #include "style.hpp"
#include "usage.hpp" #include "usage.hpp"
#include "util.hpp"
#include "version.hpp" #include "version.hpp"
#include "fix/fix.hpp" #include "fix/fix.hpp"
@@ -89,25 +92,17 @@ static Usage usage = {
// clang-format on // clang-format on
static void parseByte(uint16_t &output, char name) { static void parseByte(uint16_t &output, char name) {
if (musl_optarg[0] == 0) { if (musl_optarg[0] == '\0') {
fatal("Argument to option '-%c' may not be empty", name); fatal("Argument to option '-%c' may not be empty", name);
} }
char *endptr; if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
unsigned long value;
if (musl_optarg[0] == '$') {
value = strtoul(&musl_optarg[1], &endptr, 16);
} else {
value = strtoul(musl_optarg, &endptr, 0);
}
if (*endptr) {
fatal("Expected number as argument to option '-%c', got \"%s\"", name, musl_optarg); fatal("Expected number as argument to option '-%c', got \"%s\"", name, musl_optarg);
} else if (value > 0xFF) { } else if (value > 0xFF) {
fatal("Argument to option '-%c' is larger than 255: %lu", name, value); fatal("Argument to option '-%c' is larger than 255: %" PRIu64, name, *value);
} else {
output = *value;
} }
output = value;
} }
static uint8_t const nintendoLogo[] = { static uint8_t const nintendoLogo[] = {

View File

@@ -2,9 +2,11 @@
#include "fix/mbc.hpp" #include "fix/mbc.hpp"
#include <optional>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
@@ -97,15 +99,11 @@ bool mbc_HasRAM(MbcType type) {
} }
static void skipBlankSpace(char const *&ptr) { static void skipBlankSpace(char const *&ptr) {
while (isBlankSpace(*ptr)) { ptr += strspn(ptr, " \t");
++ptr;
}
} }
static void skipMBCSpace(char const *&ptr) { static void skipMBCSpace(char const *&ptr) {
while (isBlankSpace(*ptr) || *ptr == '_') { ptr += strspn(ptr, " \t_");
++ptr;
}
} }
static char normalizeMBCChar(char c) { static char normalizeMBCChar(char c) {
@@ -128,53 +126,42 @@ static bool readMBCSlice(char const *&name, char const *expected) {
} }
[[noreturn]] [[noreturn]]
static void fatalUnknownMBC(char const *fullName) { static void fatalUnknownMBC(char const *name) {
fatal("Unknown MBC \"%s\"\n%s", fullName, acceptedMBCNames); fatal("Unknown MBC \"%s\"\n%s", name, acceptedMBCNames);
} }
[[noreturn]] [[noreturn]]
static void fatalWrongMBCFeatures(char const *fullName) { static void fatalWrongMBCFeatures(char const *name) {
fatal("Features incompatible with MBC (\"%s\")\n%s", fullName, acceptedMBCNames); fatal("Features incompatible with MBC (\"%s\")\n%s", name, acceptedMBCNames);
} }
MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor) { MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor) {
char const *fullName = name; char const *ptr = name;
skipBlankSpace(ptr); // Trim off leading blank space
if (!strcasecmp(name, "help") || !strcasecmp(name, "list")) { if (!strcasecmp(ptr, "help") || !strcasecmp(ptr, "list")) {
puts(acceptedMBCNames); // Outputs to stdout and appends a newline puts(acceptedMBCNames); // Outputs to stdout and appends a newline
exit(0); exit(0);
} }
if (isDigit(name[0]) || name[0] == '$') { // Parse numeric MBC and return it as-is (unless it's too large)
int base = 0; if (char c = *ptr; isDigit(c) || c == '$' || c == '&' || c == '%') {
if (std::optional<uint64_t> mbc = parseWholeNumber(ptr); !mbc) {
if (name[0] == '$') { fatalUnknownMBC(name);
++name; } else if (*mbc > 0xFF) {
base = 16; fatal("Specified MBC ID out of range 0-255: \"%s\"", name);
} else {
return static_cast<MbcType>(*mbc);
} }
// Parse number, and return it as-is (unless it's too large)
char *endptr;
unsigned long mbc = strtoul(name, &endptr, base);
if (*endptr) {
fatalUnknownMBC(fullName);
}
if (mbc > 0xFF) {
fatal("Specified MBC ID out of range 0-255: \"%s\"", fullName);
}
return static_cast<MbcType>(mbc);
} }
// Begin by reading the MBC type: // Begin by reading the MBC type:
uint16_t mbc; uint16_t mbc;
char const *ptr = name;
skipBlankSpace(ptr); // Trim off leading blank space
#define tryReadSlice(expected) \ #define tryReadSlice(expected) \
do { \ do { \
if (!readMBCSlice(ptr, expected)) { \ if (!readMBCSlice(ptr, expected)) { \
fatalUnknownMBC(fullName); \ fatalUnknownMBC(name); \
} \ } \
} while (0) } while (0)
@@ -201,7 +188,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
case 'c': case 'c':
break; break;
default: default:
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
switch (*ptr++) { switch (*ptr++) {
case '1': case '1':
@@ -223,7 +210,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
mbc = MBC7_SENSOR_RUMBLE_RAM_BATTERY; mbc = MBC7_SENSOR_RUMBLE_RAM_BATTERY;
break; break;
default: default:
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
break; break;
case 'M': case 'M':
@@ -232,7 +219,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
mbc = MMM01; mbc = MMM01;
break; break;
default: default:
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
break; break;
@@ -260,33 +247,27 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
// Parse version // Parse version
skipMBCSpace(ptr); skipMBCSpace(ptr);
// Major // Major
char *endptr; if (std::optional<uint64_t> major = parseNumber(ptr, BASE_10); !major) {
unsigned long val = strtoul(ptr, &endptr, 10);
if (endptr == ptr) {
fatal("Failed to parse TPP1 major revision number"); fatal("Failed to parse TPP1 major revision number");
} } else if (*major != 1) {
ptr = endptr;
if (val != 1) {
fatal("RGBFIX only supports TPP1 version 1.0"); fatal("RGBFIX only supports TPP1 version 1.0");
} else {
tpp1Major = *major;
} }
tpp1Major = val;
tryReadSlice("."); tryReadSlice(".");
// Minor // Minor
val = strtoul(ptr, &endptr, 10); if (std::optional<uint64_t> minor = parseNumber(ptr, BASE_10); !minor) {
if (endptr == ptr) {
fatal("Failed to parse TPP1 minor revision number"); fatal("Failed to parse TPP1 minor revision number");
} } else if (*minor > 0xFF) {
ptr = endptr;
if (val > 0xFF) {
fatal("TPP1 minor revision number must be 8-bit"); fatal("TPP1 minor revision number must be 8-bit");
} else {
tpp1Minor = *minor;
} }
tpp1Minor = val;
mbc = TPP1; mbc = TPP1;
break; break;
} }
default: default:
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
break; break;
@@ -301,12 +282,12 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
mbc = HUC3; mbc = HUC3;
break; break;
default: default:
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
break; break;
default: default:
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
// Read "additional features" // Read "additional features"
@@ -330,7 +311,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
// We expect a '+' at this point // We expect a '+' at this point
skipMBCSpace(ptr); skipMBCSpace(ptr);
if (*ptr++ != '+') { if (*ptr++ != '+') {
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
skipMBCSpace(ptr); skipMBCSpace(ptr);
@@ -361,7 +342,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
features |= RAM; features |= RAM;
break; break;
default: default:
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
break; break;
@@ -378,7 +359,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
break; break;
default: default:
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
} }
#undef tryReadSlice #undef tryReadSlice
@@ -402,7 +383,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
} else if (features == (RAM | BATTERY)) { } else if (features == (RAM | BATTERY)) {
mbc += 2; mbc += 2;
} else if (features) { } else if (features) {
fatalWrongMBCFeatures(fullName); fatalWrongMBCFeatures(name);
} }
break; break;
@@ -410,7 +391,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
if (features == BATTERY) { if (features == BATTERY) {
mbc = MBC2_BATTERY; mbc = MBC2_BATTERY;
} else if (features) { } else if (features) {
fatalWrongMBCFeatures(fullName); fatalWrongMBCFeatures(name);
} }
break; break;
@@ -434,7 +415,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
} else if (features == (RAM | BATTERY)) { } else if (features == (RAM | BATTERY)) {
mbc += 2; mbc += 2;
} else if (features) { } else if (features) {
fatalWrongMBCFeatures(fullName); fatalWrongMBCFeatures(name);
} }
break; break;
@@ -452,7 +433,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
} else if (features == (RAM | BATTERY)) { } else if (features == (RAM | BATTERY)) {
mbc += 2; mbc += 2;
} else if (features) { } else if (features) {
fatalWrongMBCFeatures(fullName); fatalWrongMBCFeatures(name);
} }
break; break;
@@ -462,19 +443,19 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
case HUC3: case HUC3:
// No extra features accepted // No extra features accepted
if (features) { if (features) {
fatalWrongMBCFeatures(fullName); fatalWrongMBCFeatures(name);
} }
break; break;
case MBC7_SENSOR_RUMBLE_RAM_BATTERY: case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) { if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
fatalWrongMBCFeatures(fullName); fatalWrongMBCFeatures(name);
} }
break; break;
case HUC1_RAM_BATTERY: case HUC1_RAM_BATTERY:
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
fatalWrongMBCFeatures(fullName); fatalWrongMBCFeatures(name);
} }
break; break;
@@ -495,7 +476,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
mbc |= 0x01; mbc |= 0x01;
} }
if (features & SENSOR) { if (features & SENSOR) {
fatalWrongMBCFeatures(fullName); fatalWrongMBCFeatures(name);
} }
// Multiple rumble speeds imply rumble // Multiple rumble speeds imply rumble
if (mbc & 0x01) { if (mbc & 0x01) {
@@ -508,7 +489,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
// If there is still something left, error out // If there is still something left, error out
if (*ptr) { if (*ptr) {
fatalUnknownMBC(fullName); fatalUnknownMBC(name);
} }
return static_cast<MbcType>(mbc); return static_cast<MbcType>(mbc);

View File

@@ -3,10 +3,10 @@
#include "gfx/main.hpp" #include "gfx/main.hpp"
#include <algorithm> #include <algorithm>
#include <ctype.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <ios> #include <ios>
#include <optional>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@@ -122,76 +122,19 @@ static Usage usage = {
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters. // Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
// Returns the provided errVal on error. // Returns the provided errVal on error.
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) { static uint16_t readNumber(char const *&str, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
uint8_t base = 10; if (std::optional<uint64_t> number = parseNumber(str); !number) {
if (*string == '\0') {
error("%s: expected number, but found nothing", errPrefix); error("%s: expected number, but found nothing", errPrefix);
return errVal; return errVal;
} else if (*string == '$') { } else if (*number > UINT16_MAX) {
base = 16; error("%s: the number is too large!", errPrefix);
++string;
} else if (*string == '%') {
base = 2;
++string;
} else if (*string == '0' && string[1] != '\0') {
// Check if we have a "0x" or "0b" here
if (string[1] == 'x' || string[1] == 'X') {
base = 16;
string += 2;
} else if (string[1] == 'b' || string[1] == 'B') {
base = 2;
string += 2;
}
}
// Turns a digit into its numeric value in the current base, if it has one.
// Maximum is inclusive. The string_view is modified to "consume" all digits.
// Returns 255 on parse failure (including wrong char for base), in which case
// the string_view may be pointing on garbage.
auto charIndex = [&base](unsigned char c) -> uint8_t {
unsigned char index = c - '0'; // Use wrapping semantics
if (base == 2 && index >= 2) {
return 255;
} else if (index < 10) {
return index;
} else if (base != 16) {
return 255; // Letters are only valid in hex
}
index = tolower(c) - 'a'; // OK because we pass an `unsigned char`
if (index < 6) {
return index + 10;
}
return 255;
};
if (charIndex(*string) == 255) {
error(
"%s: expected digit%s, but found nothing", errPrefix, base != 10 ? " after base" : ""
);
return errVal; return errVal;
} else {
return *number;
} }
uint16_t number = 0;
do {
// Read a character, and check if it's valid in the given base
uint8_t index = charIndex(*string);
if (index == 255) {
break; // Found an invalid character, end
}
++string;
number *= base;
number += index;
// The lax check covers the addition on top of the multiplication
if (number >= UINT16_MAX / base) {
error("%s: the number is too large!", errPrefix);
return errVal;
}
} while (*string != '\0'); // No more characters?
return number;
} }
static void skipBlankSpace(char *&arg) { static void skipBlankSpace(char const *&arg) {
arg += strspn(arg, " \t"); arg += strspn(arg, " \t");
} }
@@ -263,7 +206,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
// to an "at-file" path if one is encountered. // to an "at-file" path if one is encountered.
static char *parseArgv(int argc, char *argv[]) { static char *parseArgv(int argc, char *argv[]) {
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
char *arg = musl_optarg; // Make a copy for scanning char const *arg = musl_optarg; // Make a copy for scanning
switch (ch) { switch (ch) {
case 'A': case 'A':
@@ -283,7 +226,7 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'b': { case 'b': {
uint16_t number = parseNumber(arg, "Bank 0 base tile ID", 0); uint16_t number = readNumber(arg, "Bank 0 base tile ID", 0);
if (number >= 256) { if (number >= 256) {
error("Bank 0 base tile ID must be below 256"); error("Bank 0 base tile ID must be below 256");
} else { } else {
@@ -303,7 +246,7 @@ static char *parseArgv(int argc, char *argv[]) {
} }
++arg; // Skip comma ++arg; // Skip comma
skipBlankSpace(arg); skipBlankSpace(arg);
number = parseNumber(arg, "Bank 1 base tile ID", 0); number = readNumber(arg, "Bank 1 base tile ID", 0);
if (number >= 256) { if (number >= 256) {
error("Bank 1 base tile ID must be below 256"); error("Bank 1 base tile ID must be below 256");
} else { } else {
@@ -346,7 +289,7 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'd': case 'd':
options.bitDepth = parseNumber(arg, "Bit depth", 2); options.bitDepth = readNumber(arg, "Bit depth", 2);
if (*arg != '\0') { if (*arg != '\0') {
error("Bit depth ('-b') argument must be a valid number, not \"%s\"", musl_optarg); error("Bit depth ('-b') argument must be a valid number, not \"%s\"", musl_optarg);
} else if (options.bitDepth != 1 && options.bitDepth != 2) { } else if (options.bitDepth != 1 && options.bitDepth != 2) {
@@ -368,7 +311,7 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'L': case 'L':
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate"); options.inputSlice.left = readNumber(arg, "Input slice left coordinate");
if (options.inputSlice.left > INT16_MAX) { if (options.inputSlice.left > INT16_MAX) {
error("Input slice left coordinate is out of range!"); error("Input slice left coordinate is out of range!");
break; break;
@@ -380,7 +323,7 @@ static char *parseArgv(int argc, char *argv[]) {
} }
++arg; ++arg;
skipBlankSpace(arg); skipBlankSpace(arg);
options.inputSlice.top = parseNumber(arg, "Input slice upper coordinate"); options.inputSlice.top = readNumber(arg, "Input slice upper coordinate");
skipBlankSpace(arg); skipBlankSpace(arg);
if (*arg != ':') { if (*arg != ':') {
error("Missing colon after upper coordinate in \"%s\"", musl_optarg); error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
@@ -388,7 +331,7 @@ static char *parseArgv(int argc, char *argv[]) {
} }
++arg; ++arg;
skipBlankSpace(arg); skipBlankSpace(arg);
options.inputSlice.width = parseNumber(arg, "Input slice width"); options.inputSlice.width = readNumber(arg, "Input slice width");
skipBlankSpace(arg); skipBlankSpace(arg);
if (options.inputSlice.width == 0) { if (options.inputSlice.width == 0) {
error("Input slice width may not be 0!"); error("Input slice width may not be 0!");
@@ -399,7 +342,7 @@ static char *parseArgv(int argc, char *argv[]) {
} }
++arg; ++arg;
skipBlankSpace(arg); skipBlankSpace(arg);
options.inputSlice.height = parseNumber(arg, "Input slice height"); options.inputSlice.height = readNumber(arg, "Input slice height");
if (options.inputSlice.height == 0) { if (options.inputSlice.height == 0) {
error("Input slice height may not be 0!"); error("Input slice height may not be 0!");
} }
@@ -409,7 +352,7 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'l': { case 'l': {
uint16_t number = parseNumber(arg, "Base palette ID", 0); uint16_t number = readNumber(arg, "Base palette ID", 0);
if (*arg != '\0') { if (*arg != '\0') {
error("Base palette ID must be a valid number, not \"%s\"", musl_optarg); error("Base palette ID must be a valid number, not \"%s\"", musl_optarg);
} else if (number >= 256) { } else if (number >= 256) {
@@ -430,7 +373,7 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'N': case 'N':
options.maxNbTiles[0] = parseNumber(arg, "Number of tiles in bank 0", 256); options.maxNbTiles[0] = readNumber(arg, "Number of tiles in bank 0", 256);
if (options.maxNbTiles[0] > 256) { if (options.maxNbTiles[0] > 256) {
error("Bank 0 cannot contain more than 256 tiles"); error("Bank 0 cannot contain more than 256 tiles");
} }
@@ -448,7 +391,7 @@ static char *parseArgv(int argc, char *argv[]) {
} }
++arg; // Skip comma ++arg; // Skip comma
skipBlankSpace(arg); skipBlankSpace(arg);
options.maxNbTiles[1] = parseNumber(arg, "Number of tiles in bank 1", 256); options.maxNbTiles[1] = readNumber(arg, "Number of tiles in bank 1", 256);
if (options.maxNbTiles[1] > 256) { if (options.maxNbTiles[1] > 256) {
error("Bank 1 cannot contain more than 256 tiles"); error("Bank 1 cannot contain more than 256 tiles");
} }
@@ -462,7 +405,7 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'n': { case 'n': {
uint16_t number = parseNumber(arg, "Number of palettes", 256); uint16_t number = readNumber(arg, "Number of palettes", 256);
if (*arg != '\0') { if (*arg != '\0') {
error("Number of palettes ('-n') must be a valid number, not \"%s\"", musl_optarg); error("Number of palettes ('-n') must be a valid number, not \"%s\"", musl_optarg);
} }
@@ -513,7 +456,7 @@ static char *parseArgv(int argc, char *argv[]) {
case 'r': case 'r':
localOptions.reverse = true; localOptions.reverse = true;
options.reversedWidth = parseNumber(arg, "Reversed image stride"); options.reversedWidth = readNumber(arg, "Reversed image stride");
if (*arg != '\0') { if (*arg != '\0') {
error( error(
"Reversed image stride ('-r') must be a valid number, not \"%s\"", musl_optarg "Reversed image stride ('-r') must be a valid number, not \"%s\"", musl_optarg
@@ -522,7 +465,7 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 's': case 's':
options.nbColorsPerPal = parseNumber(arg, "Number of colors per palette", 4); options.nbColorsPerPal = readNumber(arg, "Number of colors per palette", 4);
if (*arg != '\0') { if (*arg != '\0') {
error("Palette size ('-s') must be a valid number, not \"%s\"", musl_optarg); error("Palette size ('-s') must be a valid number, not \"%s\"", musl_optarg);
} }
@@ -564,7 +507,7 @@ static char *parseArgv(int argc, char *argv[]) {
break; break;
case 'x': case 'x':
options.trim = parseNumber(arg, "Number of tiles to trim", 0); options.trim = readNumber(arg, "Number of tiles to trim", 0);
if (*arg != '\0') { if (*arg != '\0') {
error("Tile trim ('-x') argument must be a valid number, not \"%s\"", musl_optarg); error("Tile trim ('-x') argument must be a valid number, not \"%s\"", musl_optarg);
} }

View File

@@ -9,6 +9,7 @@
#include <numeric> #include <numeric>
#include <optional> #include <optional>
#include <queue> #include <queue>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <type_traits> #include <type_traits>

View File

@@ -106,10 +106,6 @@ static yy::parser::symbol_type parseDecNumber(int c) {
return yy::parser::make_number(number); return yy::parser::make_number(number);
} }
static bool isBinDigit(int c) {
return c == '0' || c == '1';
}
static yy::parser::symbol_type parseBinNumber(char const *prefix) { static yy::parser::symbol_type parseBinNumber(char const *prefix) {
LexerStackEntry &context = lexerStack.back(); LexerStackEntry &context = lexerStack.back();
int c = context.file.sgetc(); int c = context.file.sgetc();
@@ -167,7 +163,7 @@ static yy::parser::symbol_type parseHexNumber(char const *prefix) {
return yy::parser::make_number(number); return yy::parser::make_number(number);
} }
static yy::parser::symbol_type parseNumber(int c) { static yy::parser::symbol_type parseAnyNumber(int c) {
LexerStackEntry &context = lexerStack.back(); LexerStackEntry &context = lexerStack.back();
if (c == '0') { if (c == '0') {
switch (context.file.sgetc()) { switch (context.file.sgetc()) {
@@ -265,7 +261,7 @@ yy::parser::symbol_type yylex() {
} else if (c == '&') { } else if (c == '&') {
return parseOctNumber("'&'"); return parseOctNumber("'&'");
} else if (isDigit(c)) { } else if (isDigit(c)) {
return parseNumber(c); return parseAnyNumber(c);
} else if (isLetter(c)) { } else if (isLetter(c)) {
std::string keyword = readKeyword(c); std::string keyword = readKeyword(c);

View File

@@ -4,6 +4,7 @@
#include <inttypes.h> #include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <optional>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@@ -265,21 +266,18 @@ static void parseScrambleSpec(char *spec) {
uint16_t limit = search->second.second; uint16_t limit = search->second.second;
if (regionSize) { if (regionSize) {
char *endptr; char const *ptr = regionSize + skipBlankSpace(regionSize);
unsigned long value = strtoul(regionSize, &endptr, 0); if (std::optional<uint64_t> value = parseWholeNumber(ptr); !value) {
if (*endptr != '\0') {
fatal("Invalid region size limit \"%s\" for option '-S'", regionSize); fatal("Invalid region size limit \"%s\" for option '-S'", regionSize);
} } else if (*value > limit) {
if (value > limit) {
fatal( fatal(
"%s region size for option '-S' must be between 0 and %" PRIu16, "%s region size for option '-S' must be between 0 and %" PRIu16,
search->first.c_str(), search->first.c_str(),
limit limit
); );
} else {
limit = *value;
} }
limit = value;
} else if (search->second.first != &options.scrambleWRAMX) { } else if (search->second.first != &options.scrambleWRAMX) {
// Only WRAMX limit can be implied, since ROMX and SRAM size may vary. // Only WRAMX limit can be implied, since ROMX and SRAM size may vary.
fatal("Missing %s region size limit for option '-S'", search->first.c_str()); fatal("Missing %s region size limit for option '-S'", search->first.c_str());
@@ -353,21 +351,16 @@ int main(int argc, char *argv[]) {
options.outputFileName = musl_optarg; options.outputFileName = musl_optarg;
break; break;
case 'p': { case 'p':
char *endptr; if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
unsigned long value = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') {
fatal("Invalid argument for option '-p'"); fatal("Invalid argument for option '-p'");
} } else if (*value > 0xFF) {
if (value > 0xFF) {
fatal("Argument for option '-p' must be between 0 and 0xFF"); fatal("Argument for option '-p' must be between 0 and 0xFF");
} else {
options.padValue = *value;
options.hasPadValue = true;
} }
options.padValue = value;
options.hasPadValue = true;
break; break;
}
case 'S': case 'S':
parseScrambleSpec(musl_optarg); parseScrambleSpec(musl_optarg);

View File

@@ -2,9 +2,9 @@
#include "link/sdas_obj.hpp" #include "link/sdas_obj.hpp"
#include <ctype.h>
#include <inttypes.h> #include <inttypes.h>
#include <memory> #include <memory>
#include <optional>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -15,18 +15,13 @@
#include "helpers.hpp" // assume, literal_strlen #include "helpers.hpp" // assume, literal_strlen
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "platform.hpp" #include "platform.hpp"
#include "util.hpp" // parseWholeNumber
#include "link/fstack.hpp" #include "link/fstack.hpp"
#include "link/section.hpp" #include "link/section.hpp"
#include "link/symbol.hpp" #include "link/symbol.hpp"
#include "link/warning.hpp" #include "link/warning.hpp"
enum NumberType {
HEX = 16, // X
DEC = 10, // D
OCT = 8, // Q
};
struct Location { struct Location {
FileStackNode const *src; FileStackNode const *src;
uint32_t lineNo; uint32_t lineNo;
@@ -84,36 +79,26 @@ static int nextLine(std::vector<char> &lineBuf, Location &where, FILE *file) {
} }
} }
static uint32_t readNumber(char const *str, char const *&endptr, NumberType base) { static uint64_t readNumber(Location const &where, char const *str, NumberBase base) {
for (uint32_t res = 0;;) { std::optional<uint64_t> res = parseWholeNumber(str, base);
static char const *digits = "0123456789ABCDEF";
char const *ptr = strchr(digits, toupper(*str));
if (!ptr || ptr - digits >= base) { if (!res) {
endptr = str;
return res;
}
++str;
res = res * base + (ptr - digits);
}
}
static uint32_t parseNumber(Location const &where, char const *str, NumberType base) {
if (str[0] == '\0') {
fatalAt(where, "Expected number, got empty string");
}
char const *endptr;
uint32_t res = readNumber(str, endptr, base);
if (*endptr != '\0') {
fatalAt(where, "Expected number, got \"%s\"", str); fatalAt(where, "Expected number, got \"%s\"", str);
} }
return res; return *res;
} }
static uint8_t parseByte(Location const &where, char const *str, NumberType base) { static uint32_t readInt(Location const &where, char const *str, NumberBase base) {
uint32_t num = parseNumber(where, str, base); uint64_t num = readNumber(where, str, base);
if (num > UINT32_MAX) {
fatalAt(where, "\"%s\" is not an int", str);
}
return num;
}
static uint8_t readByte(Location const &where, char const *str, NumberBase base) {
uint64_t num = readNumber(where, str, base);
if (num > UINT8_MAX) { if (num > UINT8_MAX) {
fatalAt(where, "\"%s\" is not a byte", str); fatalAt(where, "\"%s\" is not a byte", str);
@@ -184,18 +169,18 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
int lineType = nextLine(line, where, file); int lineType = nextLine(line, where, file);
// The first letter (thus, the line type) identifies the integer type // The first letter (thus, the line type) identifies the integer type
NumberType numberType; NumberBase numberBase;
switch (lineType) { switch (lineType) {
case EOF: case EOF:
fatalAt(where, "SDCC object only contains comments and empty lines"); fatalAt(where, "SDCC object only contains comments and empty lines");
case 'X': case 'X':
numberType = HEX; numberBase = BASE_16;
break; break;
case 'D': case 'D':
numberType = DEC; numberBase = BASE_10;
break; break;
case 'Q': case 'Q':
numberType = OCT; numberBase = BASE_8;
break; break;
default: default:
fatalAt( fatalAt(
@@ -239,12 +224,12 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
// Expected format: "A areas S global symbols" // Expected format: "A areas S global symbols"
getToken(line.data(), "Empty 'H' line"); getToken(line.data(), "Empty 'H' line");
uint32_t expectedNbAreas = parseNumber(where, token, numberType); uint32_t expectedNbAreas = readInt(where, token, numberBase);
expectToken("areas", 'H'); expectToken("areas", 'H');
getToken(nullptr, "'H' line is too short"); getToken(nullptr, "'H' line is too short");
uint32_t expectedNbSymbols = parseNumber(where, token, numberType); uint32_t expectedNbSymbols = readInt(where, token, numberBase);
fileSymbols.reserve(expectedNbSymbols); fileSymbols.reserve(expectedNbSymbols);
expectToken("global", 'H'); expectToken("global", 'H');
@@ -296,7 +281,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
getToken(nullptr, "'A' line is too short"); getToken(nullptr, "'A' line is too short");
uint32_t tmp = parseNumber(where, token, numberType); uint32_t tmp = readInt(where, token, numberBase);
if (tmp > UINT16_MAX) { if (tmp > UINT16_MAX) {
fatalAt( fatalAt(
@@ -310,7 +295,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
expectToken("flags", 'A'); expectToken("flags", 'A');
getToken(nullptr, "'A' line is too short"); getToken(nullptr, "'A' line is too short");
tmp = parseNumber(where, token, numberType); tmp = readInt(where, token, numberBase);
if (tmp & (1 << AREA_PAGING)) { if (tmp & (1 << AREA_PAGING)) {
fatalAt(where, "Paging is not supported"); fatalAt(where, "Paging is not supported");
} }
@@ -329,7 +314,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
expectToken("addr", 'A'); expectToken("addr", 'A');
getToken(nullptr, "'A' line is too short"); getToken(nullptr, "'A' line is too short");
tmp = parseNumber(where, token, numberType); tmp = readInt(where, token, numberBase);
curSection->org = tmp; // Truncation keeps the address portion only curSection->org = tmp; // Truncation keeps the address portion only
curSection->bank = tmp >> 16; curSection->bank = tmp >> 16;
@@ -386,7 +371,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
getToken(nullptr, "'S' line is too short"); getToken(nullptr, "'S' line is too short");
if (int32_t value = parseNumber(where, &token[3], numberType); !fileSections.empty()) { if (int32_t value = readInt(where, &token[3], numberBase); !fileSections.empty()) {
// Symbols in sections are labels; their value is an offset // Symbols in sections are labels; their value is an offset
Section *section = fileSections.back().section.get(); Section *section = fileSections.back().section.get();
if (section->isAddressFixed) { if (section->isAddressFixed) {
@@ -465,7 +450,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
data.clear(); data.clear();
for (token = strtok(line.data(), delim); token; token = strtok(nullptr, delim)) { for (token = strtok(line.data(), delim); token; token = strtok(nullptr, delim)) {
data.push_back(parseByte(where, token, numberType)); data.push_back(readByte(where, token, numberBase));
} }
if (data.size() < addrSize) { if (data.size() < addrSize) {
@@ -487,9 +472,9 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
uint16_t areaIdx; uint16_t areaIdx;
getToken(nullptr, "'R' line is too short"); getToken(nullptr, "'R' line is too short");
areaIdx = parseByte(where, token, numberType); areaIdx = readByte(where, token, numberBase);
getToken(nullptr, "'R' line is too short"); getToken(nullptr, "'R' line is too short");
areaIdx |= static_cast<uint16_t>(parseByte(where, token, numberType)) << 8; areaIdx |= static_cast<uint16_t>(readByte(where, token, numberBase)) << 8;
if (areaIdx >= fileSections.size()) { if (areaIdx >= fileSections.size()) {
fatalAt( fatalAt(
where, where,
@@ -549,16 +534,16 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
// appropriate RPN expression (depending on flags), plus an addition for the // appropriate RPN expression (depending on flags), plus an addition for the
// bytes being patched over. // bytes being patched over.
while ((token = strtok(nullptr, delim)) != nullptr) { while ((token = strtok(nullptr, delim)) != nullptr) {
uint16_t flags = parseByte(where, token, numberType); uint16_t flags = readByte(where, token, numberBase);
if ((flags & 0xF0) == 0xF0) { if ((flags & 0xF0) == 0xF0) {
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
flags = (flags & 0x0F) flags = (flags & 0x0F)
| static_cast<uint16_t>(parseByte(where, token, numberType)) << 4; | static_cast<uint16_t>(readByte(where, token, numberBase)) << 4;
} }
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
uint8_t offset = parseByte(where, token, numberType); uint8_t offset = readByte(where, token, numberBase);
if (offset < addrSize) { if (offset < addrSize) {
fatalAt( fatalAt(
@@ -578,10 +563,10 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
} }
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
uint16_t idx = parseByte(where, token, numberType); uint16_t idx = readByte(where, token, numberBase);
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
idx |= static_cast<uint16_t>(parseByte(where, token, numberType)); idx |= static_cast<uint16_t>(readByte(where, token, numberBase));
// Loudly fail on unknown flags // Loudly fail on unknown flags
if (flags & (1 << RELOC_ZPAGE | 1 << RELOC_NPAGE)) { if (flags & (1 << RELOC_ZPAGE | 1 << RELOC_NPAGE)) {

View File

@@ -2,8 +2,11 @@
#include "util.hpp" #include "util.hpp"
#include <errno.h>
#include <optional>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> // strspn
#include "helpers.hpp" // assume #include "helpers.hpp" // assume
@@ -31,6 +34,10 @@ bool isDigit(int c) {
return c >= '0' && c <= '9'; return c >= '0' && c <= '9';
} }
bool isBinDigit(int c) {
return c == '0' || c == '1';
}
bool isOctDigit(int c) { bool isOctDigit(int c) {
return c >= '0' && c <= '7'; return c >= '0' && c <= '7';
} }
@@ -64,6 +71,97 @@ uint8_t parseHexDigit(int c) {
} }
} }
// Parses a number from a string, moving the pointer to skip the parsed characters.
std::optional<uint64_t> parseNumber(char const *&str, NumberBase base) {
// Identify the base if not specified
// Does *not* support '+' or '-' sign prefix (unlike `strtoul` and `std::from_chars`)
if (base == BASE_AUTO) {
// Skips leading blank space (like `strtoul`)
str += strspn(str, " \t");
// Supports traditional ("0b", "0o", "0x") and RGBASM ('%', '&', '$') base prefixes
switch (str[0]) {
case '%':
base = BASE_2;
++str;
break;
case '&':
base = BASE_8;
++str;
break;
case '$':
base = BASE_16;
++str;
break;
case '0':
switch (str[1]) {
case 'B':
case 'b':
base = BASE_2;
str += 2;
break;
case 'O':
case 'o':
base = BASE_8;
str += 2;
break;
case 'X':
case 'x':
base = BASE_16;
str += 2;
break;
default:
base = BASE_10;
break;
}
break;
default:
base = BASE_10;
break;
}
}
assume(base != BASE_AUTO);
// Get the digit-condition function corresponding to the base
bool (*canParseDigit)(int c) = base == BASE_2 ? isBinDigit
: base == BASE_8 ? isOctDigit
: base == BASE_10 ? isDigit
: base == BASE_16 ? isHexDigit
: nullptr; // LCOV_EXCL_LINE
assume(canParseDigit != nullptr);
char const * const startDigits = str;
// Parse the number one digit at a time
// Does *not* support '_' digit separators
uint64_t result = 0;
for (; canParseDigit(str[0]); ++str) {
uint8_t digit = parseHexDigit(str[0]);
if (result > (UINT64_MAX - digit) / base) {
// Skip remaining digits and set errno = ERANGE on overflow
while (canParseDigit(str[0])) {
++str;
}
result = UINT64_MAX;
errno = ERANGE;
break;
}
result = result * base + digit;
}
// Return the parsed number if there were any digit characters
if (str - startDigits == 0) {
return std::nullopt;
}
return result;
}
// Parses a number from an entire string, returning nothing if there are more unparsed characters.
std::optional<uint64_t> parseWholeNumber(char const *str, NumberBase base) {
std::optional<uint64_t> result = parseNumber(str, base);
return str[0] == '\0' ? result : std::nullopt;
}
char const *printChar(int c) { char const *printChar(int c) {
// "'A'" + '\0': 4 bytes // "'A'" + '\0': 4 bytes
// "'\\n'" + '\0': 5 bytes // "'\\n'" + '\0': 5 bytes

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 B