mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Improve string/interpolation formatting (#1491)
- The '#' component for type 's' now escapes the string characters - The '#' component for type 'f' now prints a precision suffix - The new 'q' component specifies a precision value
This commit is contained in:
@@ -22,16 +22,16 @@ void FormatSpec::useCharacter(int c) {
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN)
|
||||
goto invalid;
|
||||
state = FORMAT_PREFIX;
|
||||
state = FORMAT_EXACT;
|
||||
sign = c;
|
||||
break;
|
||||
|
||||
// prefix
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_PREFIX)
|
||||
if (state > FORMAT_EXACT)
|
||||
goto invalid;
|
||||
state = FORMAT_ALIGN;
|
||||
prefix = true;
|
||||
exact = true;
|
||||
break;
|
||||
|
||||
// align
|
||||
@@ -42,7 +42,7 @@ void FormatSpec::useCharacter(int c) {
|
||||
alignLeft = true;
|
||||
break;
|
||||
|
||||
// pad and width
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
if (state < FORMAT_WIDTH)
|
||||
padZero = true;
|
||||
@@ -63,11 +63,14 @@ void FormatSpec::useCharacter(int c) {
|
||||
width = width * 10 + (c - '0');
|
||||
} else if (state == FORMAT_FRAC) {
|
||||
fracWidth = fracWidth * 10 + (c - '0');
|
||||
} else if (state == FORMAT_PREC) {
|
||||
precision = precision * 10 + (c - '0');
|
||||
} else {
|
||||
goto invalid;
|
||||
}
|
||||
break;
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH)
|
||||
goto invalid;
|
||||
@@ -75,6 +78,14 @@ void FormatSpec::useCharacter(int c) {
|
||||
hasFrac = true;
|
||||
break;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC)
|
||||
goto invalid;
|
||||
state = FORMAT_PREC;
|
||||
hasPrec = true;
|
||||
break;
|
||||
|
||||
// type
|
||||
case 'd':
|
||||
case 'u':
|
||||
@@ -103,6 +114,36 @@ void FormatSpec::finishCharacters() {
|
||||
state = FORMAT_INVALID;
|
||||
}
|
||||
|
||||
static std::string escapeString(std::string const &str) {
|
||||
std::string escaped;
|
||||
for (char c : str) {
|
||||
// Escape characters that need escaping
|
||||
switch (c) {
|
||||
case '\\':
|
||||
case '"':
|
||||
case '{':
|
||||
escaped += '\\';
|
||||
[[fallthrough]];
|
||||
default:
|
||||
escaped += c;
|
||||
break;
|
||||
case '\n':
|
||||
escaped += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
escaped += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
escaped += "\\t";
|
||||
break;
|
||||
case '\0':
|
||||
escaped += "\\0";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
void FormatSpec::appendString(std::string &str, std::string const &value) const {
|
||||
int useType = type;
|
||||
if (isEmpty()) {
|
||||
@@ -112,42 +153,45 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
|
||||
|
||||
if (sign)
|
||||
error("Formatting string with sign flag '%c'\n", sign);
|
||||
if (prefix)
|
||||
error("Formatting string with prefix flag '#'\n");
|
||||
if (padZero)
|
||||
error("Formatting string with padding flag '0'\n");
|
||||
if (hasFrac)
|
||||
error("Formatting string with fractional width\n");
|
||||
if (hasPrec)
|
||||
error("Formatting string with fractional precision\n");
|
||||
if (useType != 's')
|
||||
error("Formatting string as type '%c'\n", useType);
|
||||
|
||||
size_t valueLen = value.length();
|
||||
std::string useValue = exact ? escapeString(value) : value;
|
||||
size_t valueLen = useValue.length();
|
||||
size_t totalLen = width > valueLen ? width : valueLen;
|
||||
size_t padLen = totalLen - valueLen;
|
||||
|
||||
str.reserve(str.length() + totalLen);
|
||||
if (alignLeft) {
|
||||
str.append(value);
|
||||
str.append(useValue);
|
||||
str.append(padLen, ' ');
|
||||
} else {
|
||||
str.append(padLen, ' ');
|
||||
str.append(value);
|
||||
str.append(useValue);
|
||||
}
|
||||
}
|
||||
|
||||
void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
int useType = type;
|
||||
bool usePrefix = prefix;
|
||||
bool useExact = exact;
|
||||
if (isEmpty()) {
|
||||
// No format was specified; default to uppercase $hex
|
||||
useType = 'X';
|
||||
usePrefix = true;
|
||||
useExact = true;
|
||||
}
|
||||
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && usePrefix)
|
||||
error("Formatting type '%c' with prefix flag '#'\n", useType);
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f' && useExact)
|
||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
||||
if (useType != 'f' && hasFrac)
|
||||
error("Formatting type '%c' with fractional width\n", useType);
|
||||
if (useType != 'f' && hasPrec)
|
||||
error("Formatting type '%c' with fractional precision\n", useType);
|
||||
if (useType == 's')
|
||||
error("Formatting number as type 's'\n");
|
||||
|
||||
@@ -161,7 +205,7 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
}
|
||||
|
||||
char prefixChar = !usePrefix ? 0
|
||||
char prefixChar = !useExact ? 0
|
||||
: useType == 'X' ? '$'
|
||||
: useType == 'x' ? '$'
|
||||
: useType == 'b' ? '%'
|
||||
@@ -188,14 +232,27 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
|
||||
// Default fractional width (C++'s is 6 for "%f"; here 5 is enough for Q16.16)
|
||||
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
||||
|
||||
if (useFracWidth > 255) {
|
||||
error("Fractional width %zu too long, limiting to 255\n", useFracWidth);
|
||||
useFracWidth = 255;
|
||||
}
|
||||
|
||||
double fval = fabs(value / fix_PrecisionFactor());
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval);
|
||||
size_t defaultPrec = fix_Precision();
|
||||
size_t usePrec = hasPrec ? precision : defaultPrec;
|
||||
if (usePrec < 1 || usePrec > 31) {
|
||||
error(
|
||||
"Fixed-point constant precision %zu invalid, defaulting to %zu\n",
|
||||
usePrec,
|
||||
defaultPrec
|
||||
);
|
||||
usePrec = defaultPrec;
|
||||
}
|
||||
|
||||
double fval = fabs(value / pow(2.0, usePrec));
|
||||
if (useExact)
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", (int)useFracWidth, fval, usePrec);
|
||||
else
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval);
|
||||
} else if (useType == 'd') {
|
||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
||||
|
||||
Reference in New Issue
Block a user