Files
rgbds/src/asm/format.cpp

288 lines
6.8 KiB
C++

// SPDX-License-Identifier: MIT
#include "asm/format.hpp"
#include <algorithm>
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include "util.hpp" // parseNumber
#include "asm/main.hpp" // options
#include "asm/warning.hpp"
size_t FormatSpec::parseSpec(char const *spec) {
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>
if (char c = spec[i]; c == ' ' || c == '+') {
++i;
sign = c;
}
// <exact>
if (spec[i] == '#') {
++i;
exact = true;
}
// <align>
if (spec[i] == '-') {
++i;
alignLeft = true;
}
// <pad>
if (spec[i] == '0') {
++i;
padZero = true;
}
// <width>
if (isDigit(spec[i])) {
width = parseSpecNumber();
}
// <frac>
if (spec[i] == '.') {
++i;
hasFrac = true;
fracWidth = parseSpecNumber();
}
// <prec>
if (spec[i] == 'q') {
++i;
hasPrec = true;
precision = parseSpecNumber();
}
// <type>
switch (char c = spec[i]; c) {
case 'd':
case 'u':
case 'X':
case 'x':
case 'b':
case 'o':
case 'f':
case 's':
++i;
type = c;
break;
}
// Done parsing
parsed = true;
return i;
}
static std::string escapeString(std::string const &str) {
std::string escaped;
for (char c : str) {
// Escape characters that need escaping
switch (c) {
case '\n':
escaped += "\\n";
break;
case '\r':
escaped += "\\r";
break;
case '\t':
escaped += "\\t";
break;
case '\0':
escaped += "\\0";
break;
case '\\':
case '"':
case '{':
escaped += '\\';
[[fallthrough]];
default:
escaped += c;
break;
}
}
return escaped;
}
void FormatSpec::appendString(std::string &str, std::string const &value) const {
int useType = type;
if (!useType) {
// No format was specified
useType = 's';
}
if (sign) {
error("Formatting string with sign flag '%c'", sign);
}
if (padZero) {
error("Formatting string with padding flag '0'");
}
if (hasFrac) {
error("Formatting string with fractional width");
}
if (hasPrec) {
error("Formatting string with fractional precision");
}
if (useType != 's') {
error("Formatting string as type '%c'", useType);
}
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(useValue);
str.append(padLen, ' ');
} else {
str.append(padLen, ' ');
str.append(useValue);
}
}
void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
int useType = type;
bool useExact = exact;
if (!useType) {
// No format was specified; default to uppercase $hex
useType = 'X';
useExact = true;
}
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
&& useExact) {
error("Formatting type '%c' with exact flag '#'", useType);
}
if (useType != 'f' && hasFrac) {
error("Formatting type '%c' with fractional width", useType);
}
if (useType != 'f' && hasPrec) {
error("Formatting type '%c' with fractional precision", useType);
}
if (useType == 's') {
error("Formatting number as type 's'");
}
char signChar = sign; // 0 or ' ' or '+'
if (useType == 'd' || useType == 'f') {
if (int32_t v = value; v < 0) {
signChar = '-';
if (v != INT32_MIN) { // -INT32_MIN is UB
value = -v;
}
}
}
// The longest possible formatted number is fixed-point with 10 digits, 255 fractional digits,
// and a precision suffix, for 270 total bytes (counting the NUL terminator).
// (Actually 269 since a 2-digit precision cannot reach 10 integer digits.)
// Make the buffer somewhat larger just in case.
char valueBuf[300];
if (useType == 'b') {
// Special case for binary (since `snprintf` doesn't support it)
// Buffer the digits from least to greatest
char *ptr = valueBuf;
do {
*ptr++ = (value & 1) + '0';
value >>= 1;
} while (value);
// Reverse the digits and terminate the string
std::reverse(valueBuf, ptr);
*ptr = '\0';
} else if (useType == 'f') {
// Special case for fixed-point (since it needs fractional part and precision)
// 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", useFracWidth);
useFracWidth = 255;
}
// Default precision taken from default `-Q` option
size_t defaultPrec = options.fixPrecision;
size_t usePrec = hasPrec ? precision : defaultPrec;
if (usePrec < 1 || usePrec > 31) {
error(
"Fixed-point constant precision %zu invalid, defaulting to %zu",
usePrec,
defaultPrec
);
usePrec = defaultPrec;
}
// Floating-point formatting works for all fixed-point values
double fval = fabs(value / pow(2.0, usePrec));
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
} else {
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
}
} else {
// `value` has already been made non-negative, so type 'd' is OK here even for `INT32_MIN`.
// The sign will be printed later from `signChar`.
char const *spec = useType == 'd' || useType == 'u' ? "%" PRIu32
: useType == 'X' ? "%" PRIX32
: useType == 'x' ? "%" PRIx32
: useType == 'o' ? "%" PRIo32
: "%" PRIu32;
snprintf(valueBuf, sizeof(valueBuf), spec, value);
}
char prefixChar = !useExact ? 0
: useType == 'X' || useType == 'x' ? '$'
: useType == 'b' ? '%'
: useType == 'o' ? '&'
: 0;
size_t valueLen = strlen(valueBuf);
size_t numLen = (signChar != 0) + (prefixChar != 0) + valueLen;
size_t totalLen = width > numLen ? width : numLen;
size_t padLen = totalLen - numLen;
str.reserve(str.length() + totalLen);
if (alignLeft) {
if (signChar) {
str += signChar;
}
if (prefixChar) {
str += prefixChar;
}
str.append(valueBuf);
str.append(padLen, ' ');
} else {
if (padZero) {
// sign, then prefix, then zero padding
if (signChar) {
str += signChar;
}
if (prefixChar) {
str += prefixChar;
}
str.append(padLen, '0');
} else {
// space padding, then sign, then prefix
str.append(padLen, ' ');
if (signChar) {
str += signChar;
}
if (prefixChar) {
str += prefixChar;
}
}
str.append(valueBuf);
}
}