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:
Sylvie
2024-09-01 12:54:26 -04:00
committed by GitHub
parent 2fb76ce584
commit 6b8d33529e
13 changed files with 128 additions and 48 deletions

View File

@@ -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