forked from OctaForge/libostd
revamped format module (more flexible, cleaner api)
parent
3a82495a4c
commit
5a76f29dea
632
ostd/format.hh
632
ostd/format.hh
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
namespace ostd {
|
namespace ostd {
|
||||||
|
|
||||||
enum FormatFlags {
|
enum format_flags {
|
||||||
FMT_FLAG_DASH = 1 << 0,
|
FMT_FLAG_DASH = 1 << 0,
|
||||||
FMT_FLAG_ZERO = 1 << 1,
|
FMT_FLAG_ZERO = 1 << 1,
|
||||||
FMT_FLAG_SPACE = 1 << 2,
|
FMT_FLAG_SPACE = 1 << 2,
|
||||||
|
@ -30,6 +30,20 @@ struct format_error: std::runtime_error {
|
||||||
using std::runtime_error::runtime_error;
|
using std::runtime_error::runtime_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct format_spec;
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename T, typename R, typename = std::enable_if_t<std::is_same_v<
|
||||||
|
decltype(std::declval<T const &>().to_format(
|
||||||
|
std::declval<R &>(), std::declval<format_spec const &>()
|
||||||
|
)), void
|
||||||
|
>>
|
||||||
|
>
|
||||||
|
inline void to_format(T const &v, R &writer, format_spec const &fs) {
|
||||||
|
v.to_format(writer, fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* implementation helpers */
|
||||||
namespace detail {
|
namespace detail {
|
||||||
inline int parse_fmt_flags(string_range &fmt, int ret) {
|
inline int parse_fmt_flags(string_range &fmt, int ret) {
|
||||||
while (!fmt.empty()) {
|
while (!fmt.empty()) {
|
||||||
|
@ -112,9 +126,31 @@ namespace detail {
|
||||||
{ "0b", "0", "", "0x" }
|
{ "0b", "0", "", "0x" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* non-printable escapes up to 0x20 (space) */
|
||||||
|
static constexpr char const *fmt_escapes[] = {
|
||||||
|
"\\0" , "\\x01", "\\x02", "\\x03", "\\x04", "\\x05",
|
||||||
|
"\\x06", "\\a" , "\\b" , "\\t" , "\\n" , "\\v" ,
|
||||||
|
"\\f" , "\\r" , "\\x0E", "\\x0F", "\\x10", "\\x11",
|
||||||
|
"\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
|
||||||
|
"\\x18", "\\x19", "\\x1A", "\\x1B", "\\x1C", "\\x1D",
|
||||||
|
"\\x1E", "\\x1F",
|
||||||
|
/* we want to escape double quotes... */
|
||||||
|
nullptr, nullptr, "\\\"", nullptr, nullptr, nullptr,
|
||||||
|
nullptr, "\\\'"
|
||||||
|
};
|
||||||
|
|
||||||
|
inline char const *escape_fmt_char(char v, char quote) {
|
||||||
|
if ((v >= 0 && v < 0x20) || (v == quote)) {
|
||||||
|
return fmt_escapes[size_t(v)];
|
||||||
|
} else if (v == 0x7F) {
|
||||||
|
return "\\x7F";
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
/* retrieve width/precision */
|
/* retrieve width/precision */
|
||||||
template<typename T, typename ...A>
|
template<typename T, typename ...A>
|
||||||
int get_arg_param(size_t idx, T const &val, A const &...args) {
|
inline int get_arg_param(size_t idx, T const &val, A const &...args) {
|
||||||
if (idx) {
|
if (idx) {
|
||||||
if constexpr(!sizeof...(A)) {
|
if constexpr(!sizeof...(A)) {
|
||||||
throw format_error{"not enough format args"};
|
throw format_error{"not enough format args"};
|
||||||
|
@ -125,10 +161,53 @@ namespace detail {
|
||||||
if constexpr(!std::is_integral_v<T>) {
|
if constexpr(!std::is_integral_v<T>) {
|
||||||
throw format_error{"invalid argument for width/precision"};
|
throw format_error{"invalid argument for width/precision"};
|
||||||
} else {
|
} else {
|
||||||
|
if constexpr(std::is_signed_v<T>) {
|
||||||
|
if (val < 0) {
|
||||||
|
throw format_error{
|
||||||
|
"width/precision cannot be negative"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
return int(val);
|
return int(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* fallback test for whether a value can be converted to string */
|
||||||
|
template<typename T>
|
||||||
|
static std::true_type test_fmt_tostr(
|
||||||
|
decltype(ostd::to_string<T>{}(std::declval<T>())) *
|
||||||
|
);
|
||||||
|
template<typename>
|
||||||
|
static std::false_type test_fmt_tostr(...);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr bool fmt_tostr_test = decltype(test_fmt_tostr<T>(0))::value;
|
||||||
|
|
||||||
|
/* ugly ass check for whether a type is tuple-like, like tuple itself,
|
||||||
|
* pair, array, possibly other types added later or overridden...
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
std::true_type tuple_like_test(typename std::tuple_size<T>::type *);
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
std::false_type tuple_like_test(...);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr bool is_tuple_like = decltype(tuple_like_test<T>(0))::value;
|
||||||
|
|
||||||
|
/* test whether to_format works */
|
||||||
|
template<typename T, typename R>
|
||||||
|
static std::true_type test_tofmt(decltype(to_format(
|
||||||
|
std::declval<T const &>(), std::declval<R &>(),
|
||||||
|
std::declval<format_spec const &>()
|
||||||
|
)) *);
|
||||||
|
|
||||||
|
template<typename, typename>
|
||||||
|
static std::false_type test_tofmt(...);
|
||||||
|
|
||||||
|
template<typename T, typename R>
|
||||||
|
constexpr bool fmt_tofmt_test = decltype(test_tofmt<T, R>(0))::value;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct format_spec {
|
struct format_spec {
|
||||||
|
@ -137,12 +216,8 @@ struct format_spec {
|
||||||
p_nested_escape(escape), p_fmt(fmt)
|
p_nested_escape(escape), p_fmt(fmt)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
format_spec(char spec, int width = -1, int prec = -1, int flags = 0):
|
format_spec(char spec, int flags = 0):
|
||||||
p_flags(flags),
|
p_flags(flags),
|
||||||
p_width((width >= 0) ? width : 0),
|
|
||||||
p_precision((prec >= 0) ? prec : 0),
|
|
||||||
p_has_width(width >= 0),
|
|
||||||
p_has_precision(prec >= 0),
|
|
||||||
p_spec(spec)
|
p_spec(spec)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -166,41 +241,10 @@ struct format_spec {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename R>
|
|
||||||
void write_spaces(R &writer, size_t n, bool left, char c = ' ') const {
|
|
||||||
if (left == bool(p_flags & FMT_FLAG_DASH)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int w = p_width - int(n); --w >= 0; writer.put(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
string_range rest() const {
|
string_range rest() const {
|
||||||
return p_fmt;
|
return p_fmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename R>
|
|
||||||
R &&build_spec(R &&out, string_range spec) const {
|
|
||||||
out.put('%');
|
|
||||||
if (p_flags & FMT_FLAG_DASH ) {
|
|
||||||
out.put('-');
|
|
||||||
}
|
|
||||||
if (p_flags & FMT_FLAG_ZERO ) {
|
|
||||||
out.put('0');
|
|
||||||
}
|
|
||||||
if (p_flags & FMT_FLAG_SPACE) {
|
|
||||||
out.put(' ');
|
|
||||||
}
|
|
||||||
if (p_flags & FMT_FLAG_PLUS ) {
|
|
||||||
out.put('+');
|
|
||||||
}
|
|
||||||
if (p_flags & FMT_FLAG_HASH ) {
|
|
||||||
out.put('#');
|
|
||||||
}
|
|
||||||
range_put_all(out, string_range{"*.*"});
|
|
||||||
range_put_all(out, spec);
|
|
||||||
return std::forward<R>(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
int width() const { return p_width; }
|
int width() const { return p_width; }
|
||||||
int precision() const { return p_precision; }
|
int precision() const { return p_precision; }
|
||||||
|
|
||||||
|
@ -213,11 +257,25 @@ struct format_spec {
|
||||||
template<typename ...A>
|
template<typename ...A>
|
||||||
void set_width(size_t idx, A const &...args) {
|
void set_width(size_t idx, A const &...args) {
|
||||||
p_width = detail::get_arg_param(idx, args...);
|
p_width = detail::get_arg_param(idx, args...);
|
||||||
|
p_has_width = p_arg_width = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_width(int v) {
|
||||||
|
p_width = v;
|
||||||
|
p_has_width = true;
|
||||||
|
p_arg_width = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ...A>
|
template<typename ...A>
|
||||||
void set_precision(size_t idx, A const &...args) {
|
void set_precision(size_t idx, A const &...args) {
|
||||||
p_precision = detail::get_arg_param(idx, args...);
|
p_precision = detail::get_arg_param(idx, args...);
|
||||||
|
p_has_precision = p_arg_precision = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_precision(int v) {
|
||||||
|
p_precision = v;
|
||||||
|
p_has_precision = true;
|
||||||
|
p_arg_precision = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int flags() const { return p_flags; }
|
int flags() const { return p_flags; }
|
||||||
|
@ -232,7 +290,19 @@ struct format_spec {
|
||||||
bool is_nested() const { return p_is_nested; }
|
bool is_nested() const { return p_is_nested; }
|
||||||
bool nested_escape() const { return p_nested_escape; }
|
bool nested_escape() const { return p_nested_escape; }
|
||||||
|
|
||||||
protected:
|
template<typename R, typename ...A>
|
||||||
|
inline R &&format(R &&writer, A const &...args) {
|
||||||
|
write_fmt(writer, args...);
|
||||||
|
return std::forward<R>(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename R, typename T>
|
||||||
|
inline R &&format_value(R &&writer, T const &val) {
|
||||||
|
write_arg(writer, 0, val);
|
||||||
|
return std::forward<R>(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
string_range p_nested;
|
string_range p_nested;
|
||||||
string_range p_nested_sep;
|
string_range p_nested_sep;
|
||||||
|
|
||||||
|
@ -297,8 +367,12 @@ protected:
|
||||||
}
|
}
|
||||||
/* found end, in that case delimiter is after spec */
|
/* found end, in that case delimiter is after spec */
|
||||||
if (p.front() == ')') {
|
if (p.front() == ')') {
|
||||||
p_nested = begin_inner.slice(0, &begin_delim[0] - &begin_inner[0]);
|
p_nested = begin_inner.slice(
|
||||||
p_nested_sep = begin_delim.slice(0, &p[0] - &begin_delim[0] - 1);
|
0, &begin_delim[0] - &begin_inner[0]
|
||||||
|
);
|
||||||
|
p_nested_sep = begin_delim.slice(
|
||||||
|
0, &p[0] - &begin_delim[0] - 1
|
||||||
|
);
|
||||||
p.pop_front();
|
p.pop_front();
|
||||||
p_fmt = p;
|
p_fmt = p;
|
||||||
p_is_nested = true;
|
p_is_nested = true;
|
||||||
|
@ -312,7 +386,9 @@ protected:
|
||||||
for (p = find(p, '%'); !p.empty(); p = find(p, '%')) {
|
for (p = find(p, '%'); !p.empty(); p = find(p, '%')) {
|
||||||
p.pop_front();
|
p.pop_front();
|
||||||
if (p.front() == ')') {
|
if (p.front() == ')') {
|
||||||
p_nested_sep = p_nested_sep.slice(0, &p[0] - &p_nested_sep[0] - 1);
|
p_nested_sep = p_nested_sep.slice(
|
||||||
|
0, &p[0] - &p_nested_sep[0] - 1
|
||||||
|
);
|
||||||
p.pop_front();
|
p.pop_front();
|
||||||
p_fmt = p;
|
p_fmt = p;
|
||||||
p_is_nested = true;
|
p_is_nested = true;
|
||||||
|
@ -410,251 +486,71 @@ protected:
|
||||||
return (sp >= 65) && (detail::fmt_specs[sp - 65] != 0);
|
return (sp >= 65) && (detail::fmt_specs[sp - 65] != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
string_range p_fmt;
|
template<typename R>
|
||||||
char p_buf[32];
|
void write_spaces(R &writer, size_t n, bool left, char c = ' ') const {
|
||||||
};
|
if (left == bool(p_flags & FMT_FLAG_DASH)) {
|
||||||
|
|
||||||
/* for custom container formatting */
|
|
||||||
|
|
||||||
template<
|
|
||||||
typename T, typename R, typename = std::enable_if_t<
|
|
||||||
std::is_same_v<decltype(std::declval<T const &>().to_format(
|
|
||||||
std::declval<R &>(), std::declval<format_spec const &>()
|
|
||||||
)), void>
|
|
||||||
>
|
|
||||||
>
|
|
||||||
inline void to_format(T const &v, R &writer, format_spec const &fs) {
|
|
||||||
v.to_format(writer, fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template<typename R, typename T>
|
|
||||||
inline void write_u(R &writer, format_spec const *fl, bool neg, T val) {
|
|
||||||
char buf[20];
|
|
||||||
size_t n = 0;
|
|
||||||
|
|
||||||
char spec = fl->spec();
|
|
||||||
if (spec == 's') spec = 'd';
|
|
||||||
byte specn = detail::fmt_specs[spec - 65];
|
|
||||||
if (specn <= 2 || specn > 7) {
|
|
||||||
throw format_error{"cannot format integers with the given spec"};
|
|
||||||
}
|
|
||||||
|
|
||||||
int base = detail::fmt_bases[specn];
|
|
||||||
if (!val) {
|
|
||||||
buf[n++] = '0';
|
|
||||||
}
|
|
||||||
for (; val; val /= base) {
|
|
||||||
buf[n++] = detail::fmt_digits[spec >= 'a'][val % base];
|
|
||||||
}
|
|
||||||
|
|
||||||
int flags = fl->flags();
|
|
||||||
bool lsgn = flags & FMT_FLAG_PLUS;
|
|
||||||
bool lsp = flags & FMT_FLAG_SPACE;
|
|
||||||
bool zero = flags & FMT_FLAG_ZERO;
|
|
||||||
bool sign = neg + lsgn + lsp;
|
|
||||||
|
|
||||||
char const *pfx = nullptr;
|
|
||||||
size_t pfxlen = 0;
|
|
||||||
if (flags & FMT_FLAG_HASH && spec != 'd') {
|
|
||||||
pfx = detail::fmt_intpfx[spec >= 'a'][specn - 3];
|
|
||||||
pfxlen = !!pfx[1] + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!zero) {
|
|
||||||
fl->write_spaces(writer, n + pfxlen + sign, true, ' ');
|
|
||||||
}
|
|
||||||
if (sign) {
|
|
||||||
writer.put(neg ? '-' : *((" \0+") + lsgn * 2));
|
|
||||||
}
|
|
||||||
range_put_all(writer, string_range{pfx, pfx + pfxlen});
|
|
||||||
if (zero) {
|
|
||||||
fl->write_spaces(writer, n + pfxlen + sign, true, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = int(n - 1); i >= 0; --i) {
|
|
||||||
writer.put(buf[i]);
|
|
||||||
}
|
|
||||||
fl->write_spaces(writer, n + sign + pfxlen, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename R, typename ...A>
|
|
||||||
static void format_impl(
|
|
||||||
R &writer, bool escape, string_range fmt, A const &...args
|
|
||||||
);
|
|
||||||
|
|
||||||
template<size_t I>
|
|
||||||
struct FmtTupleUnpacker {
|
|
||||||
template<typename R, typename T, typename ...A>
|
|
||||||
static inline void unpack(
|
|
||||||
R &writer, bool esc, string_range fmt,
|
|
||||||
T const &item, A const &...args
|
|
||||||
) {
|
|
||||||
FmtTupleUnpacker<I - 1>::unpack(
|
|
||||||
writer, esc, fmt, item, std::get<I - 1>(item), args...
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct FmtTupleUnpacker<0> {
|
|
||||||
template<typename R, typename T, typename ...A>
|
|
||||||
static inline void unpack(
|
|
||||||
R &writer, bool esc, string_range fmt,
|
|
||||||
T const &, A const &...args
|
|
||||||
) {
|
|
||||||
format_impl(writer, esc, fmt, args...);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ugly ass check for whether a type is tuple-like, like tuple itself,
|
|
||||||
* pair, array, possibly other types added later or overridden...
|
|
||||||
*/
|
|
||||||
template<typename T>
|
|
||||||
std::true_type tuple_like_test(typename std::tuple_size<T>::type *);
|
|
||||||
|
|
||||||
template<typename>
|
|
||||||
std::false_type tuple_like_test(...);
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
constexpr bool is_tuple_like = decltype(tuple_like_test<T>(0))::value;
|
|
||||||
|
|
||||||
template<typename R, typename T>
|
|
||||||
inline void format_ritem(
|
|
||||||
R &writer, bool esc, bool, string_range fmt,
|
|
||||||
T const &item, std::enable_if_t<!is_tuple_like<T>, bool> = true
|
|
||||||
) {
|
|
||||||
format_impl(writer, esc, fmt, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename R, typename T>
|
|
||||||
inline void format_ritem(
|
|
||||||
R &writer, bool esc, bool expandval, string_range fmt,
|
|
||||||
T const &item, std::enable_if_t<is_tuple_like<T>, bool> = true
|
|
||||||
) {
|
|
||||||
if (expandval) {
|
|
||||||
FmtTupleUnpacker<std::tuple_size<T>::value>::unpack(
|
|
||||||
writer, esc, fmt, item
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
format_impl(writer, esc, fmt, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename R, typename T>
|
|
||||||
inline void write_range(
|
|
||||||
R &writer, format_spec const *fl, bool escape, bool expandval,
|
|
||||||
string_range sep, T const &val,
|
|
||||||
std::enable_if_t<detail::iterable_test<T>, bool> = true
|
|
||||||
) {
|
|
||||||
auto range = ostd::iter(val);
|
|
||||||
if (range.empty()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* test first item */
|
for (int w = p_width - int(n); --w >= 0; writer.put(c));
|
||||||
format_ritem(writer, escape, expandval, fl->rest(), range.front());
|
|
||||||
range.pop_front();
|
|
||||||
/* write the rest (if any) */
|
|
||||||
for (; !range.empty(); range.pop_front()) {
|
|
||||||
range_put_all(writer, sep);
|
|
||||||
format_ritem(
|
|
||||||
writer, escape, expandval, fl->rest(), range.front()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename R, typename T>
|
template<typename R>
|
||||||
inline void write_range(
|
void build_spec(R &out, string_range spec) const {
|
||||||
R &, format_spec const *, bool, bool, string_range,
|
out.put('%');
|
||||||
T const &, std::enable_if_t<!detail::iterable_test<T>, bool> = true
|
if (p_flags & FMT_FLAG_DASH ) {
|
||||||
) {
|
out.put('-');
|
||||||
throw format_error{"invalid value for ranged format"};
|
|
||||||
}
|
}
|
||||||
|
if (p_flags & FMT_FLAG_ZERO ) {
|
||||||
template<typename T>
|
out.put('0');
|
||||||
static std::true_type test_fmt_tostr(
|
|
||||||
decltype(ostd::to_string<T>{}(std::declval<T>())) *
|
|
||||||
);
|
|
||||||
template<typename>
|
|
||||||
static std::false_type test_fmt_tostr(...);
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
constexpr bool fmt_tostr_test = decltype(test_fmt_tostr<T>(0))::value;
|
|
||||||
|
|
||||||
/* non-printable escapes up to 0x20 (space) */
|
|
||||||
static constexpr char const *fmt_escapes[] = {
|
|
||||||
"\\0" , "\\x01", "\\x02", "\\x03", "\\x04", "\\x05",
|
|
||||||
"\\x06", "\\a" , "\\b" , "\\t" , "\\n" , "\\v" ,
|
|
||||||
"\\f" , "\\r" , "\\x0E", "\\x0F", "\\x10", "\\x11",
|
|
||||||
"\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
|
|
||||||
"\\x18", "\\x19", "\\x1A", "\\x1B", "\\x1C", "\\x1D",
|
|
||||||
"\\x1E", "\\x1F",
|
|
||||||
/* we want to escape double quotes... */
|
|
||||||
nullptr, nullptr, "\\\"", nullptr, nullptr, nullptr,
|
|
||||||
nullptr, "\\\'"
|
|
||||||
};
|
|
||||||
|
|
||||||
inline char const *escape_fmt_char(char v, char quote) {
|
|
||||||
if ((v >= 0 && v < 0x20) || (v == quote)) {
|
|
||||||
return fmt_escapes[size_t(v)];
|
|
||||||
} else if (v == 0x7F) {
|
|
||||||
return "\\x7F";
|
|
||||||
}
|
}
|
||||||
return nullptr;
|
if (p_flags & FMT_FLAG_SPACE) {
|
||||||
|
out.put(' ');
|
||||||
}
|
}
|
||||||
|
if (p_flags & FMT_FLAG_PLUS ) {
|
||||||
inline std::string escape_fmt_str(string_range val) {
|
out.put('+');
|
||||||
std::string ret;
|
|
||||||
ret.push_back('"');
|
|
||||||
while (!val.empty()) {
|
|
||||||
char const *esc = escape_fmt_char(val.front(), '"');
|
|
||||||
if (esc) {
|
|
||||||
ret.append(esc);
|
|
||||||
} else {
|
|
||||||
ret.push_back(val.front());
|
|
||||||
}
|
}
|
||||||
val.pop_front();
|
if (p_flags & FMT_FLAG_HASH ) {
|
||||||
|
out.put('#');
|
||||||
}
|
}
|
||||||
ret.push_back('"');
|
range_put_all(out, string_range{"*.*"});
|
||||||
return ret;
|
range_put_all(out, spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename R>
|
|
||||||
static std::true_type test_tofmt(decltype(to_format(
|
|
||||||
std::declval<T const &>(), std::declval<R &>(),
|
|
||||||
std::declval<format_spec const &>()
|
|
||||||
)) *);
|
|
||||||
|
|
||||||
template<typename, typename>
|
|
||||||
static std::false_type test_tofmt(...);
|
|
||||||
|
|
||||||
template<typename T, typename R>
|
|
||||||
constexpr bool fmt_tofmt_test = decltype(test_tofmt<T, R>(0))::value;
|
|
||||||
|
|
||||||
struct write_spec: format_spec {
|
|
||||||
using format_spec::format_spec;
|
|
||||||
|
|
||||||
/* string base writer */
|
/* string base writer */
|
||||||
template<typename R>
|
template<typename R>
|
||||||
void write_str(R &writer, bool escape, string_range val) const {
|
void write_str(R &writer, bool escape, string_range val) const {
|
||||||
if (escape) {
|
|
||||||
write_str(writer, false, escape_fmt_str(val));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
size_t n = val.size();
|
size_t n = val.size();
|
||||||
if (this->precision()) {
|
if (has_precision()) {
|
||||||
n = this->precision();
|
n = std::min(n, size_t(precision()));
|
||||||
}
|
}
|
||||||
this->write_spaces(writer, n, true);
|
write_spaces(writer, n, true);
|
||||||
|
if (escape) {
|
||||||
|
writer.put('"');
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
if (val.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char c = val.front();
|
||||||
|
char const *esc = detail::escape_fmt_char(c, '"');
|
||||||
|
if (esc) {
|
||||||
|
range_put_all(writer, string_range{esc});
|
||||||
|
} else {
|
||||||
|
writer.put(c);
|
||||||
|
}
|
||||||
|
val.pop_front();
|
||||||
|
}
|
||||||
|
writer.put('"');
|
||||||
|
} else {
|
||||||
range_put_all(writer, val.slice(0, n));
|
range_put_all(writer, val.slice(0, n));
|
||||||
this->write_spaces(writer, n, false);
|
}
|
||||||
|
write_spaces(writer, n, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* char values */
|
/* char values */
|
||||||
template<typename R>
|
template<typename R>
|
||||||
void write_char(R &writer, bool escape, char val) const {
|
void write_char(R &writer, bool escape, char val) const {
|
||||||
if (escape) {
|
if (escape) {
|
||||||
char const *esc = escape_fmt_char(val, '\'');
|
char const *esc = detail::escape_fmt_char(val, '\'');
|
||||||
if (esc) {
|
if (esc) {
|
||||||
char buf[6];
|
char buf[6];
|
||||||
buf[0] = '\'';
|
buf[0] = '\'';
|
||||||
|
@ -667,7 +563,7 @@ namespace detail {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->write_spaces(writer, 1 + escape * 2, true);
|
write_spaces(writer, 1 + escape * 2, true);
|
||||||
if (escape) {
|
if (escape) {
|
||||||
writer.put('\'');
|
writer.put('\'');
|
||||||
writer.put(val);
|
writer.put(val);
|
||||||
|
@ -675,17 +571,69 @@ namespace detail {
|
||||||
} else {
|
} else {
|
||||||
writer.put(val);
|
writer.put(val);
|
||||||
}
|
}
|
||||||
this->write_spaces(writer, 1 + escape * 2, false);
|
write_spaces(writer, 1 + escape * 2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename R, typename T>
|
||||||
|
void write_int(R &writer, bool ptr, bool neg, T val) const {
|
||||||
|
char buf[20];
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
char isp = spec();
|
||||||
|
if (isp == 's') {
|
||||||
|
isp = (ptr ? 'x' : 'd');
|
||||||
|
}
|
||||||
|
byte specn = detail::fmt_specs[isp - 65];
|
||||||
|
if (specn <= 2 || specn > 7) {
|
||||||
|
throw format_error{"cannot format integers with the given spec"};
|
||||||
|
}
|
||||||
|
|
||||||
|
int base = detail::fmt_bases[specn];
|
||||||
|
if (!val) {
|
||||||
|
buf[n++] = '0';
|
||||||
|
}
|
||||||
|
for (; val; val /= base) {
|
||||||
|
buf[n++] = detail::fmt_digits[isp >= 'a'][val % base];
|
||||||
|
}
|
||||||
|
|
||||||
|
int fl = flags();
|
||||||
|
bool lsgn = fl & FMT_FLAG_PLUS;
|
||||||
|
bool lsp = fl & FMT_FLAG_SPACE;
|
||||||
|
bool zero = fl & FMT_FLAG_ZERO;
|
||||||
|
bool sign = neg + lsgn + lsp;
|
||||||
|
|
||||||
|
char const *pfx = nullptr;
|
||||||
|
size_t pfxlen = 0;
|
||||||
|
if (((fl & FMT_FLAG_HASH) || ptr) && isp != 'd') {
|
||||||
|
pfx = detail::fmt_intpfx[isp >= 'a'][specn - 3];
|
||||||
|
pfxlen = !!pfx[1] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!zero) {
|
||||||
|
write_spaces(writer, n + pfxlen + sign, true, ' ');
|
||||||
|
}
|
||||||
|
if (sign) {
|
||||||
|
writer.put(neg ? '-' : *((" \0+") + lsgn * 2));
|
||||||
|
}
|
||||||
|
range_put_all(writer, string_range{pfx, pfx + pfxlen});
|
||||||
|
if (zero) {
|
||||||
|
write_spaces(writer, n + pfxlen + sign, true, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = int(n - 1); i >= 0; --i) {
|
||||||
|
writer.put(buf[i]);
|
||||||
|
}
|
||||||
|
write_spaces(writer, n + sign + pfxlen, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* floating point */
|
/* floating point */
|
||||||
template<typename R, typename T, bool Long = std::is_same_v<T, ldouble>>
|
template<typename R, typename T, bool Long = std::is_same_v<T, ldouble>>
|
||||||
void write_float(R &writer, bool, T val) const {
|
void write_float(R &writer, T val) const {
|
||||||
char buf[16], rbuf[128];
|
char buf[16], rbuf[128];
|
||||||
char fmtspec[Long + 1];
|
char fmtspec[Long + 1];
|
||||||
|
|
||||||
fmtspec[Long] = this->spec();
|
fmtspec[Long] = spec();
|
||||||
byte specn = detail::fmt_specs[this->spec() - 65];
|
byte specn = detail::fmt_specs[spec() - 65];
|
||||||
if (specn != 1 && specn != 7) {
|
if (specn != 1 && specn != 7) {
|
||||||
throw format_error{"cannot format floats with the given spec"};
|
throw format_error{"cannot format floats with the given spec"};
|
||||||
}
|
}
|
||||||
|
@ -696,10 +644,12 @@ namespace detail {
|
||||||
fmtspec[0] = 'L';
|
fmtspec[0] = 'L';
|
||||||
}
|
}
|
||||||
|
|
||||||
this->build_spec(iter(buf), fmtspec).put('\0');
|
auto bufr = iter(buf);
|
||||||
|
build_spec(bufr, fmtspec);
|
||||||
|
bufr.put('\0');
|
||||||
int ret = snprintf(
|
int ret = snprintf(
|
||||||
rbuf, sizeof(rbuf), buf, this->width(),
|
rbuf, sizeof(rbuf), buf, width(),
|
||||||
this->has_precision() ? this->precision() : 6, val
|
has_precision() ? precision() : 6, val
|
||||||
);
|
);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
/* typically unreachable, build_spec creates valid format */
|
/* typically unreachable, build_spec creates valid format */
|
||||||
|
@ -711,8 +661,8 @@ namespace detail {
|
||||||
/* this should typically never happen */
|
/* this should typically never happen */
|
||||||
dbuf = new char[ret + 1];
|
dbuf = new char[ret + 1];
|
||||||
ret = snprintf(
|
ret = snprintf(
|
||||||
dbuf, ret + 1, buf, this->width(),
|
dbuf, ret + 1, buf, width(),
|
||||||
this->has_precision() ? this->precision() : 6, val
|
has_precision() ? precision() : 6, val
|
||||||
);
|
);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
/* see above */
|
/* see above */
|
||||||
|
@ -728,13 +678,13 @@ namespace detail {
|
||||||
template<typename R, typename T>
|
template<typename R, typename T>
|
||||||
void write_val(R &writer, bool escape, T const &val) const {
|
void write_val(R &writer, bool escape, T const &val) const {
|
||||||
/* stuff fhat can be custom-formatted goes first */
|
/* stuff fhat can be custom-formatted goes first */
|
||||||
if constexpr(fmt_tofmt_test<T, noop_output_range<char>>) {
|
if constexpr(detail::fmt_tofmt_test<T, noop_output_range<char>>) {
|
||||||
to_format(val, writer, *this);
|
to_format(val, writer, *this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* second best, we can convert to string slice */
|
/* second best, we can convert to string slice */
|
||||||
if constexpr(std::is_constructible_v<string_range, T const &>) {
|
if constexpr(std::is_constructible_v<string_range, T const &>) {
|
||||||
if (this->spec() != 's') {
|
if (spec() != 's') {
|
||||||
throw format_error{"strings need the '%s' spec"};
|
throw format_error{"strings need the '%s' spec"};
|
||||||
}
|
}
|
||||||
write_str(writer, escape, val);
|
write_str(writer, escape, val);
|
||||||
|
@ -742,7 +692,7 @@ namespace detail {
|
||||||
}
|
}
|
||||||
/* bools, check if printing as string, otherwise convert to int */
|
/* bools, check if printing as string, otherwise convert to int */
|
||||||
if constexpr(std::is_same_v<T, bool>) {
|
if constexpr(std::is_same_v<T, bool>) {
|
||||||
if (this->spec() == 's') {
|
if (spec() == 's') {
|
||||||
write_val(writer, ("false\0true") + (6 * val));
|
write_val(writer, ("false\0true") + (6 * val));
|
||||||
} else {
|
} else {
|
||||||
write_val(writer, int(val));
|
write_val(writer, int(val));
|
||||||
|
@ -751,7 +701,7 @@ namespace detail {
|
||||||
}
|
}
|
||||||
/* character values */
|
/* character values */
|
||||||
if constexpr(std::is_same_v<T, char>) {
|
if constexpr(std::is_same_v<T, char>) {
|
||||||
if (this->spec() != 's' && this->spec() != 'c') {
|
if (spec() != 's' && spec() != 'c') {
|
||||||
throw format_error{"cannot format chars with the given spec"};
|
throw format_error{"cannot format chars with the given spec"};
|
||||||
}
|
}
|
||||||
write_char(writer, escape, val);
|
write_char(writer, escape, val);
|
||||||
|
@ -761,13 +711,7 @@ namespace detail {
|
||||||
* char pointers are handled by the string case above
|
* char pointers are handled by the string case above
|
||||||
*/
|
*/
|
||||||
if constexpr(std::is_pointer_v<T>) {
|
if constexpr(std::is_pointer_v<T>) {
|
||||||
format_spec sp{
|
write_int(writer, (spec() == 's'), false, size_t(val));
|
||||||
(spec() == 's') ? 'x' : spec(),
|
|
||||||
has_width() ? width() : -1,
|
|
||||||
has_precision() ? precision() : -1,
|
|
||||||
(spec() == 's') ? (flags() | FMT_FLAG_HASH) : flags()
|
|
||||||
};
|
|
||||||
detail::write_u(writer, &sp, false, size_t(val));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* integers */
|
/* integers */
|
||||||
|
@ -775,24 +719,24 @@ namespace detail {
|
||||||
if constexpr(std::is_signed_v<T>) {
|
if constexpr(std::is_signed_v<T>) {
|
||||||
/* signed integers */
|
/* signed integers */
|
||||||
using UT = std::make_unsigned_t<T>;
|
using UT = std::make_unsigned_t<T>;
|
||||||
detail::write_u(
|
write_int(
|
||||||
writer, this, val < 0,
|
writer, false, val < 0,
|
||||||
(val < 0) ? static_cast<UT>(-val) : static_cast<UT>(val)
|
(val < 0) ? static_cast<UT>(-val) : static_cast<UT>(val)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
/* unsigned integers */
|
/* unsigned integers */
|
||||||
detail::write_u(writer, this, false, val);
|
write_int(writer, false, false, val);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* floats */
|
/* floats */
|
||||||
if constexpr(std::is_floating_point_v<T>) {
|
if constexpr(std::is_floating_point_v<T>) {
|
||||||
write_float(writer, escape, val);
|
write_float(writer, val);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* stuff that can be to_string'd, worst reliable case, allocates */
|
/* stuff that can be to_string'd, worst reliable case, allocates */
|
||||||
if constexpr(fmt_tostr_test<T>) {
|
if constexpr(detail::fmt_tostr_test<T>) {
|
||||||
if (this->spec() != 's') {
|
if (spec() != 's') {
|
||||||
throw format_error{"custom objects need the '%s' spec"};
|
throw format_error{"custom objects need the '%s' spec"};
|
||||||
}
|
}
|
||||||
write_val(writer, false, ostd::to_string<T>{}(val));
|
write_val(writer, false, ostd::to_string<T>{}(val));
|
||||||
|
@ -814,7 +758,48 @@ namespace detail {
|
||||||
write_arg(writer, idx - 1, args...);
|
write_arg(writer, idx - 1, args...);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
write_val(writer, this->p_nested_escape, val);
|
write_val(writer, p_nested_escape, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename R, typename T>
|
||||||
|
inline void write_range_item(
|
||||||
|
R &writer, bool expandval, string_range fmt, T const &item
|
||||||
|
) const {
|
||||||
|
if constexpr(detail::is_tuple_like<T>) {
|
||||||
|
if (expandval) {
|
||||||
|
std::apply([&writer, esc = p_nested_escape, &fmt](
|
||||||
|
auto const &...args
|
||||||
|
) mutable {
|
||||||
|
format_spec sp{fmt, esc};
|
||||||
|
sp.write_fmt(writer, args...);
|
||||||
|
}, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format_spec sp{fmt, p_nested_escape};
|
||||||
|
sp.write_fmt(writer, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename R, typename T>
|
||||||
|
void write_range_val(
|
||||||
|
R &writer, bool expandval, string_range sep, T const &val
|
||||||
|
) const {
|
||||||
|
if constexpr(detail::iterable_test<T>) {
|
||||||
|
auto range = ostd::iter(val);
|
||||||
|
if (range.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
write_range_item(writer, expandval, rest(), range.front());
|
||||||
|
range.pop_front();
|
||||||
|
if (range.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
range_put_all(writer, sep);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw format_error{"invalid value for ranged format"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,83 +816,70 @@ namespace detail {
|
||||||
write_range(writer, idx - 1, expandval, sep, args...);
|
write_range(writer, idx - 1, expandval, sep, args...);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
detail::write_range(
|
write_range_val(writer, expandval, sep, val);
|
||||||
writer, this, this->p_nested_escape, expandval, sep, val
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
template<typename R, typename ...A>
|
template<typename R, typename ...A>
|
||||||
inline void format_impl(
|
void write_fmt(R &writer, A const &...args) {
|
||||||
R &writer, bool escape, string_range fmt, A const &...args
|
|
||||||
) {
|
|
||||||
size_t argidx = 1;
|
size_t argidx = 1;
|
||||||
detail::write_spec spec(fmt, escape);
|
while (read_until_spec(writer)) {
|
||||||
while (spec.read_until_spec(writer)) {
|
size_t argpos = index();
|
||||||
size_t argpos = spec.index();
|
if (is_nested()) {
|
||||||
if (spec.is_nested()) {
|
|
||||||
if (!argpos) {
|
if (!argpos) {
|
||||||
argpos = argidx++;
|
argpos = argidx++;
|
||||||
}
|
}
|
||||||
/* FIXME: figure out a better way */
|
/* FIXME: figure out a better way */
|
||||||
detail::write_spec nspec(spec.nested(), spec.nested_escape());
|
format_spec nspec(nested(), nested_escape());
|
||||||
nspec.write_range(
|
nspec.write_range(
|
||||||
writer, argpos - 1, (spec.flags() & FMT_FLAG_HASH),
|
writer, argpos - 1, (flags() & FMT_FLAG_HASH),
|
||||||
spec.nested_sep(), args...
|
nested_sep(), args...
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!argpos) {
|
if (!argpos) {
|
||||||
argpos = argidx++;
|
argpos = argidx++;
|
||||||
if (spec.arg_width()) {
|
if (arg_width()) {
|
||||||
spec.set_width(argpos - 1, args...);
|
set_width(argpos - 1, args...);
|
||||||
argpos = argidx++;
|
argpos = argidx++;
|
||||||
}
|
}
|
||||||
if (spec.arg_precision()) {
|
if (arg_precision()) {
|
||||||
spec.set_precision(argpos - 1, args...);
|
set_precision(argpos - 1, args...);
|
||||||
argpos = argidx++;
|
argpos = argidx++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bool argprec = spec.arg_precision();
|
bool argprec = arg_precision();
|
||||||
if (argprec) {
|
if (argprec) {
|
||||||
if (argpos <= 1) {
|
if (argpos <= 1) {
|
||||||
throw format_error{"argument precision not given"};
|
throw format_error{"argument precision not given"};
|
||||||
}
|
}
|
||||||
spec.set_precision(argpos - 2, args...);
|
set_precision(argpos - 2, args...);
|
||||||
}
|
}
|
||||||
if (spec.arg_width()) {
|
if (arg_width()) {
|
||||||
if (argpos <= (size_t(argprec) + 1)) {
|
if (argpos <= (size_t(argprec) + 1)) {
|
||||||
throw format_error{"argument width not given"};
|
throw format_error{"argument width not given"};
|
||||||
}
|
}
|
||||||
spec.set_width(argpos - 2 - argprec, args...);
|
set_width(argpos - 2 - argprec, args...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spec.write_arg(writer, argpos - 1, args...);
|
write_arg(writer, argpos - 1, args...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename R>
|
template<typename R, typename ...A>
|
||||||
inline void format_impl(R &writer, bool, string_range fmt) {
|
void write_fmt(R &writer) {
|
||||||
detail::write_spec spec(fmt, false);
|
if (read_until_spec(writer)) {
|
||||||
if (spec.read_until_spec(writer)) {
|
|
||||||
throw format_error{"format spec without format arguments"};
|
throw format_error{"format spec without format arguments"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} /* namespace detail */
|
|
||||||
|
string_range p_fmt;
|
||||||
|
char p_buf[32];
|
||||||
|
};
|
||||||
|
|
||||||
template<typename R, typename ...A>
|
template<typename R, typename ...A>
|
||||||
inline R &&format(R &&writer, string_range fmt, A const &...args) {
|
inline R &&format(R &&writer, string_range fmt, A const &...args) {
|
||||||
detail::format_impl(writer, false, fmt, args...);
|
return format_spec{fmt}.format(std::forward<R>(writer), args...);
|
||||||
return std::forward<R>(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename R, typename T>
|
|
||||||
inline R &&format(R &&writer, format_spec const &fmt, T const &val) {
|
|
||||||
/* we can do this as there are no members added... but ugly, FIXME later */
|
|
||||||
detail::write_spec const &wsp = static_cast<detail::write_spec const &>(fmt);
|
|
||||||
wsp.write_arg(writer, 0, val);
|
|
||||||
return std::forward<R>(writer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ostd */
|
} /* namespace ostd */
|
||||||
|
|
|
@ -213,7 +213,7 @@ namespace detail {
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline void write(T const &v) {
|
inline void write(T const &v) {
|
||||||
format(detail::stdout_range{}, format_spec{'s'}, v);
|
format_spec{'s'}.format_value(detail::stdout_range{}, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename ...A>
|
template<typename T, typename ...A>
|
||||||
|
|
|
@ -204,7 +204,7 @@ namespace detail {
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline void stream::write(T const &v) {
|
inline void stream::write(T const &v) {
|
||||||
format(detail::fmt_stream_range{this}, format_spec{'s'}, v);
|
format_spec{'s'}.format_value(detail::fmt_stream_range{this}, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ...A>
|
template<typename ...A>
|
||||||
|
|
|
@ -702,7 +702,7 @@ public:
|
||||||
} else {
|
} else {
|
||||||
p_buf = sbuf;
|
p_buf = sbuf;
|
||||||
}
|
}
|
||||||
char_range bufr{p_buf, p_buf + input.size()};
|
char_range bufr{p_buf, p_buf + input.size() + 1};
|
||||||
range_put_all(bufr, input);
|
range_put_all(bufr, input);
|
||||||
bufr.put('\0');
|
bufr.put('\0');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue