[String] Allow using Java style %[arg$] prefix in sprintf.

This commit is contained in:
Pāvels Nadtočajevs 2026-02-13 23:43:15 +02:00
parent be587f3a2d
commit f69edf810e
No known key found for this signature in database
GPG key ID: 8413210218EF35D2
2 changed files with 76 additions and 30 deletions

View file

@ -5176,10 +5176,13 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
static const String MINUS("-");
static const String PLUS("+");
LocalVector<bool> used_args;
used_args.resize_initialized(values.size());
String formatted;
char32_t *self = (char32_t *)get_data();
bool in_format = false;
uint64_t value_index = 0;
int selected_index = -1;
int min_chars = 0;
int min_decimals = 0;
bool in_decimals = false;
@ -5206,15 +5209,16 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
case 'o': // Octal
case 'x': // Hexadecimal (lowercase)
case 'X': { // Hexadecimal (uppercase)
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
if (!values[value_index].is_num()) {
if (!values[index].is_num()) {
return "a number is required";
}
int64_t value = values[value_index];
int64_t value = values[index];
int base = 16;
bool capitalize = false;
switch (c) {
@ -5270,21 +5274,25 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
case 'f': { // Float
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
if (!values[value_index].is_num()) {
if (!values[index].is_num()) {
return "a number is required";
}
double value = values[value_index];
double value = values[index];
bool is_negative = std::signbit(value);
String str = String::num(Math::abs(value), min_decimals);
const bool is_finite = Math::is_finite(value);
@ -5316,17 +5324,21 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
case 'v': { // Vector2/3/4/2i/3i/4i
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
int count;
switch (values[value_index].get_type()) {
switch (values[index].get_type()) {
case Variant::VECTOR2:
case Variant::VECTOR2I: {
count = 2;
@ -5344,7 +5356,7 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
}
Vector4 vec = values[value_index];
Vector4 vec = values[index];
String str = "(";
for (int i = 0; i < count; i++) {
double val = vec[i];
@ -5386,16 +5398,20 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
str += ")";
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
case 's': { // String
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
String str = values[value_index];
String str = values[index];
// Padding.
if (left_justified) {
str = str.rpad(min_chars);
@ -5404,19 +5420,23 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
case 'c': {
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
// Convert to character.
String str;
if (values[value_index].is_num()) {
int value = values[value_index];
if (values[index].is_num()) {
int value = values[index];
if (value < 0) {
return "unsigned integer is lower than minimum";
} else if (value >= 0xd800 && value <= 0xdfff) {
@ -5424,9 +5444,9 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
} else if (value > 0x10ffff) {
return "unsigned integer is greater than maximum";
}
str = chr(values[value_index]);
} else if (values[value_index].get_type() == Variant::STRING) {
str = values[value_index];
str = chr(values[index]);
} else if (values[index].get_type() == Variant::STRING) {
str = values[index];
if (str.length() != 1) {
return "%c requires number or single-character string";
}
@ -5442,7 +5462,10 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
@ -5486,6 +5509,14 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
break;
}
case '$': {
if (min_chars > 0) {
selected_index = min_chars - 1;
}
min_chars = 0;
pad_with_zeros = false;
break;
}
case '.': { // Float/Vector separator.
if (in_decimals) {
return "too many decimal points in format";
@ -5496,27 +5527,30 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
case '*': { // Dynamic width, based on value.
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
Variant::Type value_type = values[value_index].get_type();
if (!values[value_index].is_num() &&
Variant::Type value_type = values[index].get_type();
if (!values[index].is_num() &&
value_type != Variant::VECTOR2 && value_type != Variant::VECTOR2I &&
value_type != Variant::VECTOR3 && value_type != Variant::VECTOR3I &&
value_type != Variant::VECTOR4 && value_type != Variant::VECTOR4I) {
return "* wants number or vector";
}
int size = values[value_index];
int size = values[index];
if (in_decimals) {
min_decimals = size;
} else {
min_chars = size;
}
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
break;
}
@ -5535,6 +5569,7 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
left_justified = false;
show_sign = false;
in_decimals = false;
selected_index = -1;
break;
default:
formatted += c;
@ -5546,8 +5581,10 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
return "incomplete format";
}
if (value_index != values.size()) {
return "not all arguments converted during string formatting";
for (const bool &b : used_args) {
if (!b) {
return "not all arguments converted during string formatting";
}
}
if (error) {

View file

@ -1296,6 +1296,15 @@ TEST_CASE("[String] sprintf") {
REQUIRE(error == false);
CHECK(output == String("fish 99.990 frog"));
///// Argument indices.
format = "fish %2$d frog %1$s xx";
args.clear();
args.push_back("test");
args.push_back(5);
output = format.sprintf(args, &error);
REQUIRE(error == false);
CHECK(output == String("fish 5 frog test xx"));
///// Errors
// More formats than arguments.