mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 02:02:06 +00:00
Factor out a single parseNumber utility function (#1839)
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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[] = {
|
||||||
|
|||||||
111
src/fix/mbc.cpp
111
src/fix/mbc.cpp
@@ -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);
|
||||||
|
|||||||
103
src/gfx/main.cpp
103
src/gfx/main.cpp
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
98
src/util.cpp
98
src/util.cpp
@@ -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
|
||||||
|
|||||||
BIN
test/gfx/warn_group_output.png
Normal file
BIN
test/gfx/warn_group_output.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 B |
Reference in New Issue
Block a user