- formatting implemented for floating point types
- fixed some bugs with width and precision specifiers:
* Evaluation order of nested replacement fields
This commit is contained in:
@@ -132,54 +132,62 @@ string format(const cstring& str, ArgsT&&...args) {
|
||||
// we're going to parse right-to-left since the valid combinations
|
||||
// of specifiers change based on the type of the argument
|
||||
|
||||
// to compensate for this, the nested replacement fields need to be computed in this loop
|
||||
// (nested replacement deduced)
|
||||
size_t nrfd = 0;
|
||||
size_t nnrf = 0;
|
||||
|
||||
// first find the matching '}' brace, e is not necessarily the matching brace
|
||||
// since some specifiers allow nested replacement fields
|
||||
size_t spec = colon;
|
||||
while (str[spec + 1] != '}') {
|
||||
size_t parse = colon;
|
||||
while (str[parse + 1] != '}') {
|
||||
if (next_brace < end) { // if the next brace is before the next closing brace
|
||||
spec = end + 1;
|
||||
end = str.find('}', spec);
|
||||
next_brace = str.find('{', spec);
|
||||
++nnrf;
|
||||
nrfd += str[end - 1] == '{';
|
||||
parse = end + 1;
|
||||
end = str.find('}', parse);
|
||||
next_brace = str.find('{', parse);
|
||||
} else {
|
||||
spec = end - 1;
|
||||
parse = end - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(spec < str.length() - 1 and str[spec+1] == '}', "fennec::format syntax error, mismatched '{}'");
|
||||
assertf(nrfd <= 2 and parse < str.length() - 1 and str[parse + 1] == '}',
|
||||
"fennec::format syntax error, mismatched '{}'");
|
||||
|
||||
// check type
|
||||
switch (str[spec]) {
|
||||
switch (str[parse]) {
|
||||
default: break;
|
||||
|
||||
case 's': case '?': // strings
|
||||
case 'c': // char
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
|
||||
|
||||
case 'd': // decimal
|
||||
fmt.base = 10;
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
|
||||
case 'B': // binary
|
||||
fmt.upper = true; [[fallthrough]];
|
||||
case 'b':
|
||||
fmt.base = 2;
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
|
||||
case 'o': // octal
|
||||
fmt.base = 8;
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
|
||||
case 'X': // hex
|
||||
fmt.upper = true; [[fallthrough]];
|
||||
case 'x':
|
||||
fmt.base = 16;
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
|
||||
|
||||
@@ -187,33 +195,33 @@ string format(const cstring& str, ArgsT&&...args) {
|
||||
fmt.upper = true; [[fallthrough]];
|
||||
case 'a': // float hex
|
||||
fmt.base = 16;
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
|
||||
case 'E': // scientific notation
|
||||
fmt.upper = true; [[fallthrough]];
|
||||
case 'e':
|
||||
fmt.base = 16;
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
|
||||
case 'F': // fixed precision
|
||||
fmt.upper = true; [[fallthrough]];
|
||||
case 'f':
|
||||
fmt.base = 10;
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
|
||||
case 'G': // general precision
|
||||
fmt.upper = true; [[fallthrough]];
|
||||
case 'g':
|
||||
fmt.base = 10;
|
||||
fmt.type = str[spec--];
|
||||
fmt.type = str[parse--];
|
||||
break;
|
||||
}
|
||||
|
||||
// early return
|
||||
if (spec == colon) {
|
||||
if (parse == colon) {
|
||||
fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
|
||||
res += argarray.format(arg, fmt);
|
||||
i = end + 1;
|
||||
@@ -227,6 +235,9 @@ string format(const cstring& str, ArgsT&&...args) {
|
||||
bool is_float_t = detail::_isfmt_f(fmt.type);
|
||||
bool is_str_t = fmt.type == 's';
|
||||
bool is_integer_t = detail::_isfmt_i(fmt.type);
|
||||
bool ded_width_f = false;
|
||||
bool ded_width = false;
|
||||
size_t ded_temp_i = 0;
|
||||
|
||||
// default "precision" for strings should be 0 for no limit
|
||||
if (is_str_t) {
|
||||
@@ -234,7 +245,7 @@ string format(const cstring& str, ArgsT&&...args) {
|
||||
}
|
||||
|
||||
// parse width and precision
|
||||
while (isdigit(str[spec]) or (found_decimal = (str[spec] == '.')) or str[spec] == '{' or str[spec] == '}') {
|
||||
while (isdigit(str[parse]) or (found_decimal = (str[parse] == '.')) or str[parse] == '{' or str[parse] == '}') {
|
||||
// handle decimal point for precision
|
||||
if (found_decimal) {
|
||||
assertf(is_float_t or is_str_t, "fennec::format syntax error, encountered precision argument on non-floating point format");
|
||||
@@ -244,43 +255,72 @@ string format(const cstring& str, ArgsT&&...args) {
|
||||
|
||||
fmt.precision = x;
|
||||
x = 0, j = 1;
|
||||
--spec;
|
||||
--parse;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for nested replacement field
|
||||
if (str[spec] == '{') {
|
||||
assertf(str[spec - 1] == '0' or str[spec - 1] == '.' or not isdigit(str[spec - 1]),
|
||||
if (str[parse] == '{') {
|
||||
assertf(str[parse - 1] == '0' or str[parse - 1] == '.' or not isdigit(str[parse - 1]),
|
||||
"fennec::format syntax error, unexpected digit preceding nested replacement field");
|
||||
|
||||
bool prec = str[spec - 1] == '.';
|
||||
size_t sub = str[spec + 1] == '}' ? ++arg_c : x;
|
||||
bool prec = str[parse - 1] == '.';
|
||||
bool ded = str[parse + 1] == '}';
|
||||
|
||||
size_t sub;
|
||||
if (nrfd == 2) { // if both are deduced, parse normally. Hack with prefix and postfix.
|
||||
sub = prec ? ++arg_c + 1 : arg_c++;
|
||||
} else if (nrfd == 1 and nnrf == 2 and prec and ded) { // if only precision is nrf, deduce width first
|
||||
ded_width_f = true;
|
||||
ded_temp_i = parse;
|
||||
continue;
|
||||
} else { // otherwise deduce normally
|
||||
sub = ded ? ++arg_c : x;
|
||||
}
|
||||
|
||||
assertf(sub < argc, "fennec::format syntax error, argument index out of range in nested replacement field");
|
||||
assertf(argarray.is_integer(sub), "fennec::format argument error, nested replacement field argument is not convertible to integral type");
|
||||
|
||||
(prec ? fmt.precision : fmt.width) = argarray.int_value(sub);
|
||||
x = 0;
|
||||
|
||||
if (ded_width_f) {
|
||||
ded_width_f = false;
|
||||
ded_width = true;
|
||||
swap(ded_temp_i, parse);
|
||||
arg_c = sub;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ded_width) {
|
||||
parse = ded_temp_i;
|
||||
ded_width = false;
|
||||
}
|
||||
|
||||
parse -= 1 + prec;
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore closing brace for nested replacement fields
|
||||
if (str[spec] == '}') {
|
||||
if (str[parse] == '}') {
|
||||
--parse;
|
||||
continue;
|
||||
}
|
||||
|
||||
// crude way to only handle 0 case if 0 is the last digit
|
||||
fmt.fill = str[spec] == '0' ? '0' : ' ';
|
||||
fmt.fill = str[parse] == '0' ? '0' : ' ';
|
||||
|
||||
// parse the number
|
||||
x += j * (str[spec] - '0');
|
||||
x += j * (str[parse] - '0');
|
||||
j *= 10;
|
||||
--spec;
|
||||
--parse;
|
||||
}
|
||||
if (x != 0) {
|
||||
fmt.width = x;
|
||||
}
|
||||
|
||||
// early return
|
||||
if (spec == colon) {
|
||||
if (parse == colon) {
|
||||
fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
|
||||
res += argarray.format(arg, fmt);
|
||||
i = end + 1;
|
||||
@@ -288,41 +328,41 @@ string format(const cstring& str, ArgsT&&...args) {
|
||||
}
|
||||
|
||||
// check for alt form
|
||||
if (str[spec] == '#') {
|
||||
if (str[parse] == '#') {
|
||||
assertf(is_float_t or is_integer_t, "fennec::format syntax error, encountered alt spec ('#') with non-decimal type");
|
||||
fmt.alt = true;
|
||||
--spec;
|
||||
--parse;
|
||||
}
|
||||
|
||||
// check for sign
|
||||
if (str[spec] == '-' or str[spec] == '+' or str[spec] == ' ') {
|
||||
fmt.sign = str[spec];
|
||||
if (str[spec] == ' ') { // handle fill if only space, gets overwritten if encounters fill character
|
||||
if (str[parse] == '-' or str[parse] == '+' or str[parse] == ' ') {
|
||||
fmt.sign = str[parse];
|
||||
if (str[parse] == ' ') { // handle fill if only space, gets overwritten if encounters fill character
|
||||
fmt.fill = ' ';
|
||||
}
|
||||
--spec;
|
||||
--parse;
|
||||
}
|
||||
|
||||
// check for alignment
|
||||
if (str[spec] == '<' or str[spec] == '>' or str[spec] == '^') {
|
||||
fmt.align = str[spec];
|
||||
--spec;
|
||||
if (str[parse] == '<' or str[parse] == '>' or str[parse] == '^') {
|
||||
fmt.align = str[parse];
|
||||
--parse;
|
||||
}
|
||||
|
||||
// fill character
|
||||
if (str[spec] != ':') {
|
||||
fmt.fill = str[spec];
|
||||
if (str[spec] == ' ') {
|
||||
if (str[parse] != ':') {
|
||||
fmt.fill = str[parse];
|
||||
if (str[parse] == ' ') {
|
||||
fmt.sign = fmt.sign == '\0' ? ' ' : fmt.sign;
|
||||
}
|
||||
--spec;
|
||||
--parse;
|
||||
}
|
||||
|
||||
// default sign
|
||||
fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
|
||||
|
||||
// validate that we handled the entire format arg
|
||||
assertf(spec == colon, "fennec::format syntax error, malformed format string detected, possible double colon");
|
||||
assertf(parse == colon, "fennec::format syntax error, malformed format string detected, possible double colon");
|
||||
|
||||
|
||||
// add formatted argument
|
||||
|
||||
@@ -163,12 +163,75 @@ struct formatter<FloatT> {
|
||||
string operator()(const format_arg& fmt, FloatT x) {
|
||||
|
||||
// nan & inf cases
|
||||
if (isnan(x)) {
|
||||
if (fennec::isnan(x)) {
|
||||
return string("nan");
|
||||
}
|
||||
if (isinf(x)) {
|
||||
if (fennec::isinf(x)) {
|
||||
return string("inf");
|
||||
}
|
||||
|
||||
|
||||
char digits[128] = {};
|
||||
auto chk = fennec::to_chars(digits, digits + sizeof(digits), fennec::abs(x), fmt.type, fmt.precision);
|
||||
assertf(chk != nullptr, "fennec::format error, to_chars error");
|
||||
size_t len = chk - digits;
|
||||
|
||||
// handle uppercase
|
||||
if (fmt.upper) {
|
||||
for (auto& digit : digits) {
|
||||
if (digit == 0) {
|
||||
break;
|
||||
}
|
||||
digit = toupper(digit);
|
||||
}
|
||||
}
|
||||
|
||||
const bool has_sign = (x < 0 or fmt.sign != '-');
|
||||
const bool zero = fmt.fill == '0';
|
||||
const size_t prefix = fmt.alt ? 2 : 0;
|
||||
const size_t sgnlen = len + (zero ? has_sign + prefix : 0);
|
||||
const size_t explen = fennec::max(sgnlen, fmt.width) + (zero ? 0 : has_sign + prefix);
|
||||
const size_t fill = fmt.width > sgnlen ? fmt.width - sgnlen : 0;
|
||||
size_t sign = 0;
|
||||
|
||||
string res = string(explen);
|
||||
|
||||
if (fill > 0) {
|
||||
switch (fmt.align) {
|
||||
case '<':
|
||||
memcpy(res.data() + has_sign + prefix, digits, len);
|
||||
memset(res.data() + has_sign + prefix + len, fmt.fill == '0' ? ' ' : fmt.fill, fill);
|
||||
break;
|
||||
case '>': case '\0':
|
||||
memcpy(res.data() + explen - len, digits, len);
|
||||
sign = fmt.fill == '0' ? 0 : explen - len - 1 - prefix;
|
||||
memset(res.data(), fmt.fill, explen - len);
|
||||
break;
|
||||
case '^':
|
||||
size_t bef = fill / 2 + has_sign + prefix;
|
||||
size_t aft = explen - bef;
|
||||
memcpy(res.data() + bef, digits, len);
|
||||
sign = fmt.fill == '0' ? 0 : bef - 1 - prefix;
|
||||
memset(res.data(), fmt.fill, bef);
|
||||
memset(res.data() + bef + len, fmt.fill == '0' ? ' ' : fmt.fill, aft);
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
memcpy(res.data() + has_sign + prefix, digits, len);
|
||||
}
|
||||
|
||||
if (has_sign) {
|
||||
res[sign] = (x < 0) ? '-' : fmt.sign;
|
||||
}
|
||||
|
||||
if (prefix) {
|
||||
res[sign + has_sign] = '0';
|
||||
res[sign + has_sign + 1] = fmt.type + ('x' - 'a');
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#ifndef FENNEC_TEST_PRINTING_H
|
||||
#define FENNEC_TEST_PRINTING_H
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include <fennec/filesystem/path.h>
|
||||
@@ -63,17 +64,17 @@ inline std::ostream& operator<<(std::ostream& os, const quaternion<ScalarT>& q)
|
||||
|
||||
// Helper for printing strings
|
||||
inline std::ostream& operator<<(std::ostream& os, const cstring& str) {
|
||||
return os << str.data();
|
||||
return os << std::quoted(str.data());
|
||||
}
|
||||
|
||||
// Helper for printing strings
|
||||
inline std::ostream& operator<<(std::ostream& os, const string& str) {
|
||||
return os << str.cstr();
|
||||
return os << std::quoted(str.cstr());
|
||||
}
|
||||
|
||||
// Helper for printing strings
|
||||
inline std::ostream& operator<<(std::ostream& os, const path& str) {
|
||||
return os << str.str();
|
||||
return os << std::quoted(str.cstr());
|
||||
}
|
||||
|
||||
// Helper for printing types
|
||||
|
||||
@@ -83,6 +83,22 @@ inline void fennec_test_format() {
|
||||
|
||||
fennec_test_run(fennec::format("{},{}", true, false), string("true,false"));
|
||||
fennec_test_run(fennec::format("{:#06b},{:#06b}", true, false), string("0b0001,0b0000"));
|
||||
|
||||
fennec_test_spacer(1);
|
||||
|
||||
fennec_test_run(fennec::format("{:10f}", fennec::pi<float>()), string(" 3.141593"));
|
||||
fennec_test_run(fennec::format("{:{}f}", fennec::pi<float>(), 10), string(" 3.141593"));
|
||||
fennec_test_run(fennec::format("{:.5f}", fennec::pi<float>()), string("3.14159"));
|
||||
fennec_test_run(fennec::format("{:.{}f}", fennec::pi<float>(), 5), string("3.14159"));
|
||||
fennec_test_run(fennec::format("{:10.5f}", fennec::pi<float>()), string(" 3.14159"));
|
||||
fennec_test_run(fennec::format("{:{}.{}f}", fennec::pi<float>(), 10, 5), string(" 3.14159"));
|
||||
|
||||
fennec_test_spacer(1);
|
||||
|
||||
fennec_test_run(fennec::format("{:.5f}", fennec::pi<float>()), string("3.14159"));
|
||||
fennec_test_run(fennec::format("{:.5a}", fennec::pi<float>()), string("1.921fbp+1"));
|
||||
fennec_test_run(fennec::format("{:.5g}", fennec::pi<float>()), string("3.1416"));
|
||||
fennec_test_run(fennec::format("{:.5e}", fennec::pi<float>()), string("3.14159e+00"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user