// SPDX-License-Identifier: MIT #include "asm/format.hpp" #include #include #include #include #include #include #include #include #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; }; // if (char c = spec[i]; c == ' ' || c == '+') { ++i; sign = c; } // if (spec[i] == '#') { ++i; exact = true; } // if (spec[i] == '-') { ++i; alignLeft = true; } // if (spec[i] == '0') { ++i; padZero = true; } // if (isDigit(spec[i])) { width = parseSpecNumber(); } // if (spec[i] == '.') { ++i; hasFrac = true; fracWidth = parseSpecNumber(); } // if (spec[i] == 'q') { ++i; hasPrec = true; precision = parseSpecNumber(); } // 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(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); } }