libostd/ostd/format.hh

916 lines
28 KiB
C++
Raw Normal View History

2015-07-04 20:19:49 +02:00
/* Format strings for OctaSTD. Inspired by D's std.format module.
2015-07-01 03:22:42 +02:00
*
* This file is part of OctaSTD. See COPYING.md for futher information.
*/
2015-07-13 21:08:55 +02:00
#ifndef OSTD_FORMAT_HH
#define OSTD_FORMAT_HH
2015-07-01 03:22:42 +02:00
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
2017-01-28 19:39:50 +01:00
#include <utility>
#include <stdexcept>
2017-01-28 19:39:50 +01:00
2015-07-13 21:08:55 +02:00
#include "ostd/algorithm.hh"
#include "ostd/string.hh"
2015-07-01 03:22:42 +02:00
2015-07-13 21:07:14 +02:00
namespace ostd {
2015-07-01 03:22:42 +02:00
enum FormatFlags {
FMT_FLAG_DASH = 1 << 0,
FMT_FLAG_ZERO = 1 << 1,
FMT_FLAG_SPACE = 1 << 2,
FMT_FLAG_PLUS = 1 << 3,
FMT_FLAG_HASH = 1 << 4
};
struct format_error: std::runtime_error {
using std::runtime_error::runtime_error;
};
2015-07-01 03:29:42 +02:00
namespace detail {
2017-02-16 18:48:14 +01:00
inline int parse_fmt_flags(string_range &fmt, int ret) {
2015-07-21 22:16:38 +02:00
while (!fmt.empty()) {
switch (fmt.front()) {
2016-08-03 17:50:06 +02:00
case '-': ret |= FMT_FLAG_DASH; fmt.pop_front(); break;
case '+': ret |= FMT_FLAG_PLUS; fmt.pop_front(); break;
case '#': ret |= FMT_FLAG_HASH; fmt.pop_front(); break;
case '0': ret |= FMT_FLAG_ZERO; fmt.pop_front(); break;
case ' ': ret |= FMT_FLAG_SPACE; fmt.pop_front(); break;
default: goto retflags;
}
}
retflags:
return ret;
}
2017-02-16 18:48:14 +01:00
inline size_t read_digits(string_range &fmt, char *buf) {
2017-01-30 19:11:39 +01:00
size_t ret = 0;
2015-07-21 22:16:38 +02:00
for (; !fmt.empty() && isdigit(fmt.front()); ++ret) {
*buf++ = fmt.front();
fmt.pop_front();
}
*buf = '\0';
return ret;
}
/* 0 .. not allowed
* 1 .. floating point
* 2 .. character
* 3 .. binary
* 4 .. octal
* 5 .. decimal
* 6 .. hexadecimal
* 7 .. string
* 8 .. custom object
*/
2016-06-23 20:18:35 +02:00
static constexpr byte const fmt_specs[] = {
/* uppercase spec set */
1, 3, 8, 8, /* A B C D */
1, 1, 1, 8, /* E F G H */
8, 8, 8, 8, /* I J K L */
8, 8, 8, 8, /* M N O P */
8, 8, 8, 8, /* Q R S T */
8, 8, 8, 6, /* U V W X */
8, 8, /* Y Z */
/* ascii filler */
0, 0, 0, 0, 0, 0,
/* lowercase spec set */
1, 3, 2, 5, /* a b c d */
1, 1, 1, 8, /* e f g h */
8, 8, 8, 8, /* i j k l */
8, 8, 4, 8, /* m n o p */
8, 8, 7, 8, /* q r s t */
8, 8, 8, 6, /* u v w x */
8, 8, /* y z */
/* ascii filler */
0, 0, 0, 0, 0
};
2016-06-23 20:18:35 +02:00
static constexpr int const fmt_bases[] = {
0, 0, 0, 2, 8, 10, 16, 0
};
2016-06-23 20:18:35 +02:00
static constexpr char fmt_digits[2][16] = {
{
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
},
{
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
}
};
2016-06-23 20:18:35 +02:00
static constexpr char const *fmt_intpfx[2][4] = {
{ "0B", "0", "", "0X" },
{ "0b", "0", "", "0x" }
};
2015-07-04 20:19:49 +02:00
/* retrieve width/precision */
template<typename T, typename ...A>
int get_arg_param(size_t idx, T const &val, A const &...args) {
if (idx) {
if constexpr(!sizeof...(A)) {
throw format_error{"not enough format args"};
} else {
return get_arg_param(idx - 1, args...);
}
} else {
if constexpr(!std::is_integral_v<T>) {
throw format_error{"invalid argument for width/precision"};
} else {
return int(val);
}
}
2015-07-04 20:19:49 +02:00
}
}
2015-07-04 02:04:09 +02:00
2017-02-16 20:39:05 +01:00
struct format_spec {
format_spec(): p_nested_escape(false), p_fmt() {}
format_spec(string_range fmt, bool escape = false):
p_nested_escape(escape), p_fmt(fmt)
{}
2017-02-16 20:39:05 +01:00
format_spec(char spec, int width = -1, int prec = -1, int flags = 0):
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)
{}
template<typename R>
bool read_until_spec(R &writer) {
if (p_fmt.empty()) {
return false;
}
2015-07-21 22:16:38 +02:00
while (!p_fmt.empty()) {
if (p_fmt.front() == '%') {
p_fmt.pop_front();
if (p_fmt.front() == '%') {
goto plain;
}
return read_spec();
}
plain:
2015-07-21 22:16:38 +02:00
writer.put(p_fmt.front());
p_fmt.pop_front();
}
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;
}
2015-07-04 20:19:49 +02:00
for (int w = p_width - int(n); --w >= 0; writer.put(c));
}
2017-02-16 18:48:14 +01:00
string_range rest() const {
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);
}
2015-07-04 20:19:49 +02:00
int width() const { return p_width; }
int precision() const { return p_precision; }
bool has_width() const { return p_has_width; }
bool has_precision() const { return p_has_precision; }
bool arg_width() const { return p_arg_width; }
bool arg_precision() const { return p_arg_precision; }
template<typename ...A>
void set_width(size_t idx, A const &...args) {
p_width = detail::get_arg_param(idx, args...);
2015-07-04 20:19:49 +02:00
}
template<typename ...A>
void set_precision(size_t idx, A const &...args) {
p_precision = detail::get_arg_param(idx, args...);
2015-07-04 20:19:49 +02:00
}
int flags() const { return p_flags; }
char spec() const { return p_spec; }
byte index() const { return p_index; }
2015-07-04 20:19:49 +02:00
2017-02-16 18:48:14 +01:00
string_range nested() const { return p_nested; }
string_range nested_sep() const { return p_nested_sep; }
2015-07-04 20:19:49 +02:00
bool is_nested() const { return p_is_nested; }
bool nested_escape() const { return p_nested_escape; }
protected:
2017-02-16 18:48:14 +01:00
string_range p_nested;
string_range p_nested_sep;
2015-07-04 20:19:49 +02:00
int p_flags = 0;
int p_width = 0;
int p_precision = 0;
bool p_has_width = false;
bool p_has_precision = false;
bool p_arg_width = false;
bool p_arg_precision = false;
char p_spec = '\0';
byte p_index = 0;
2015-07-04 20:19:49 +02:00
bool p_is_nested = false;
bool p_nested_escape = false;
2015-07-04 02:04:09 +02:00
bool read_until_dummy() {
2015-07-21 22:16:38 +02:00
while (!p_fmt.empty()) {
if (p_fmt.front() == '%') {
p_fmt.pop_front();
if (p_fmt.front() == '%') {
goto plain;
}
2015-07-04 02:04:09 +02:00
return read_spec();
}
plain:
2015-07-21 22:16:38 +02:00
p_fmt.pop_front();
2015-07-04 02:04:09 +02:00
}
return false;
}
bool read_spec_range() {
int sflags = p_flags;
p_nested_escape = !(sflags & FMT_FLAG_DASH);
2015-07-21 22:16:38 +02:00
p_fmt.pop_front();
2017-02-16 18:48:14 +01:00
string_range begin_inner(p_fmt);
if (!read_until_dummy()) {
2015-07-04 20:19:49 +02:00
p_is_nested = false;
return false;
}
/* skip to the last spec in case multiple specs are present */
2017-02-16 18:48:14 +01:00
string_range curfmt(p_fmt);
while (read_until_dummy()) {
curfmt = p_fmt;
}
p_fmt = curfmt;
p_flags = sflags;
2015-07-04 02:04:09 +02:00
/* find delimiter or ending */
2017-02-16 18:48:14 +01:00
string_range begin_delim(p_fmt);
string_range p = find(begin_delim, '%');
2015-07-21 22:16:38 +02:00
for (; !p.empty(); p = find(p, '%')) {
p.pop_front();
/* escape, skip */
2015-07-21 22:16:38 +02:00
if (p.front() == '%') {
p.pop_front();
2015-07-04 02:04:09 +02:00
continue;
}
/* found end, in that case delimiter is after spec */
2015-07-21 22:16:38 +02:00
if (p.front() == ')') {
p_nested = begin_inner.slice(0, &begin_delim[0] - &begin_inner[0]);
p_nested_sep = begin_delim.slice(0, &p[0] - &begin_delim[0] - 1);
p.pop_front();
p_fmt = p;
2015-07-04 20:19:49 +02:00
p_is_nested = true;
2015-07-04 02:04:09 +02:00
return true;
}
/* found actual delimiter start... */
2015-07-21 22:16:38 +02:00
if (p.front() == '|') {
p_nested = begin_inner.slice(0, &p[0] - &begin_inner[0] - 1);
p.pop_front();
2015-07-04 20:19:49 +02:00
p_nested_sep = p;
2015-07-21 22:16:38 +02:00
for (p = find(p, '%'); !p.empty(); p = find(p, '%')) {
p.pop_front();
if (p.front() == ')') {
p_nested_sep = p_nested_sep.slice(0, &p[0] - &p_nested_sep[0] - 1);
p.pop_front();
p_fmt = p;
2015-07-04 20:19:49 +02:00
p_is_nested = true;
2015-07-04 02:04:09 +02:00
return true;
}
}
2015-07-04 20:19:49 +02:00
p_is_nested = false;
2015-07-04 02:04:09 +02:00
return false;
}
}
2015-07-04 20:19:49 +02:00
p_is_nested = false;
2015-07-04 02:04:09 +02:00
return false;
}
bool read_spec() {
2017-01-30 19:11:39 +01:00
size_t ndig = detail::read_digits(p_fmt, p_buf);
bool havepos = false;
2015-07-04 20:19:49 +02:00
p_index = 0;
/* parse index */
2015-07-21 22:16:38 +02:00
if (p_fmt.front() == '$') {
if (ndig <= 0) return false; /* no pos given */
int idx = atoi(p_buf);
if (idx <= 0 || idx > 255) return false; /* bad index */
p_index = byte(idx);
2015-07-21 22:16:38 +02:00
p_fmt.pop_front();
havepos = true;
}
/* parse flags */
2015-07-04 20:19:49 +02:00
p_flags = 0;
2017-01-30 19:11:39 +01:00
size_t skipd = 0;
if (havepos || !ndig) {
p_flags = detail::parse_fmt_flags(p_fmt, 0);
} else {
2017-01-30 19:11:39 +01:00
for (size_t i = 0; i < ndig; ++i) {
if (p_buf[i] != '0') {
break;
}
++skipd;
}
if (skipd) {
p_flags = FMT_FLAG_ZERO;
}
if (skipd == ndig) {
p_flags = detail::parse_fmt_flags(p_fmt, p_flags);
}
}
2015-07-10 01:24:47 +02:00
/* range/array formatting */
2015-07-21 22:16:38 +02:00
if ((p_fmt.front() == '(') && (havepos || !(ndig - skipd))) {
2015-07-10 01:24:47 +02:00
return read_spec_range();
}
/* parse width */
2015-07-04 20:19:49 +02:00
p_width = 0;
p_has_width = false;
p_arg_width = false;
if (!havepos && ndig && (ndig - skipd)) {
2015-07-04 20:19:49 +02:00
p_width = atoi(p_buf + skipd);
p_has_width = true;
} else if (detail::read_digits(p_fmt, p_buf)) {
2015-07-04 20:19:49 +02:00
p_width = atoi(p_buf);
p_has_width = true;
2015-07-21 22:16:38 +02:00
} else if (p_fmt.front() == '*') {
2015-07-04 20:19:49 +02:00
p_arg_width = p_has_width = true;
2015-07-21 22:16:38 +02:00
p_fmt.pop_front();
}
/* parse precision */
2015-07-04 20:19:49 +02:00
p_precision = 0;
p_has_precision = false;
p_arg_precision = false;
if (p_fmt.front() != '.') {
goto fmtchar;
}
2015-07-21 22:16:38 +02:00
p_fmt.pop_front();
if (detail::read_digits(p_fmt, p_buf)) {
2015-07-04 20:19:49 +02:00
p_precision = atoi(p_buf);
p_has_precision = true;
2015-07-21 22:16:38 +02:00
} else if (p_fmt.front() == '*') {
2015-07-04 20:19:49 +02:00
p_arg_precision = p_has_precision = true;
2015-07-21 22:16:38 +02:00
p_fmt.pop_front();
} else {
return false;
}
fmtchar:
2015-07-21 22:16:38 +02:00
p_spec = p_fmt.front();
p_fmt.pop_front();
/* make sure we're testing on a signed byte - our mapping only
* tests values up to 127 */
sbyte sp = p_spec;
return (sp >= 65) && (detail::fmt_specs[sp - 65] != 0);
}
2017-02-16 18:48:14 +01:00
string_range p_fmt;
char p_buf[32];
};
2015-07-11 20:44:58 +02:00
/* for custom container formatting */
2015-07-11 20:14:12 +02:00
template<
2017-02-09 20:56:15 +01:00
typename T, typename R, typename = std::enable_if_t<
std::is_same_v<decltype(std::declval<T const &>().to_format(
2017-02-16 20:39:05 +01:00
std::declval<R &>(), std::declval<format_spec const &>()
)), void>
2016-01-12 23:24:40 +01:00
>
>
2017-02-16 20:39:05 +01:00
inline void to_format(T const &v, R &writer, format_spec const &fs) {
v.to_format(writer, fs);
2015-07-11 20:14:12 +02:00
}
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;
2015-07-04 20:19:49 +02:00
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];
}
2015-07-04 20:19:49 +02:00
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;
2016-06-23 20:18:35 +02:00
char const *pfx = nullptr;
2017-02-11 01:28:14 +01:00
size_t pfxlen = 0;
2015-07-04 20:19:49 +02:00
if (flags & FMT_FLAG_HASH && spec != 'd') {
pfx = detail::fmt_intpfx[spec >= 'a'][specn - 3];
2015-07-03 22:19:50 +02:00
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);
}
2015-07-04 03:18:21 +02:00
template<typename R, typename ...A>
static void format_impl(
2017-02-16 18:48:14 +01:00
R &writer, bool escape, string_range fmt, A const &...args
);
2015-07-04 03:18:21 +02:00
2017-01-30 19:11:39 +01:00
template<size_t I>
struct FmtTupleUnpacker {
template<typename R, typename T, typename ...A>
static inline void unpack(
2017-02-16 18:48:14 +01:00
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(
2017-02-16 18:48:14 +01:00
R &writer, bool esc, string_range fmt,
T const &, A const &...args
) {
format_impl(writer, esc, fmt, args...);
}
};
2017-01-28 19:39:50 +01:00
/* 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;
2015-07-04 16:23:54 +02:00
template<typename R, typename T>
inline void format_ritem(
2017-02-16 18:48:14 +01:00
R &writer, bool esc, bool, string_range fmt,
2017-02-09 20:56:15 +01:00
T const &item, std::enable_if_t<!is_tuple_like<T>, bool> = true
) {
format_impl(writer, esc, fmt, item);
2015-07-04 15:52:02 +02:00
}
template<typename R, typename T>
inline void format_ritem(
2017-02-16 18:48:14 +01:00
R &writer, bool esc, bool expandval, string_range fmt,
2017-02-09 20:56:15 +01:00
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);
}
2015-07-04 15:52:02 +02:00
}
2015-07-04 03:18:21 +02:00
template<typename R, typename T>
inline void write_range(
2017-02-16 20:39:05 +01:00
R &writer, format_spec const *fl, bool escape, bool expandval,
2017-02-16 18:48:14 +01:00
string_range sep, T const &val,
2017-02-16 20:39:05 +01:00
std::enable_if_t<detail::iterable_test<T>, bool> = true
) {
2015-07-13 21:07:14 +02:00
auto range = ostd::iter(val);
if (range.empty()) {
return;
}
2015-07-04 03:18:21 +02:00
/* test first item */
format_ritem(writer, escape, expandval, fl->rest(), range.front());
2015-07-04 03:18:21 +02:00
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()
);
2015-07-04 03:18:21 +02:00
}
}
template<typename R, typename T>
inline void write_range(
2017-02-16 20:39:05 +01:00
R &, format_spec const *, bool, bool, string_range,
T const &, std::enable_if_t<!detail::iterable_test<T>, bool> = true
) {
throw format_error{"invalid value for ranged format"};
2015-07-04 03:18:21 +02:00
}
template<typename T>
2017-02-09 20:56:15 +01:00
static std::true_type test_fmt_tostr(
2017-02-16 20:39:05 +01:00
decltype(ostd::to_string<T>{}(std::declval<T>())) *
2017-02-09 20:56:15 +01:00
);
template<typename>
2017-02-09 20:56:15 +01:00
static std::false_type test_fmt_tostr(...);
2015-07-04 15:52:02 +02:00
template<typename T>
2017-02-16 20:39:05 +01:00
constexpr bool fmt_tostr_test = decltype(test_fmt_tostr<T>(0))::value;
2015-07-04 15:52:02 +02:00
/* non-printable escapes up to 0x20 (space) */
2016-06-23 20:18:35 +02:00
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, "\\\'"
};
2016-06-23 20:18:35 +02:00
inline char const *escape_fmt_char(char v, char quote) {
if ((v >= 0 && v < 0x20) || (v == quote)) {
2017-01-30 19:11:39 +01:00
return fmt_escapes[size_t(v)];
} else if (v == 0x7F) {
return "\\x7F";
}
return nullptr;
}
2017-02-16 18:48:14 +01:00
inline std::string escape_fmt_str(string_range val) {
2017-01-30 00:54:06 +01:00
std::string ret;
ret.push_back('"');
while (!val.empty()) {
2016-06-23 20:18:35 +02:00
char const *esc = escape_fmt_char(val.front(), '"');
if (esc) {
ret.append(esc);
} else {
2017-01-30 00:54:06 +01:00
ret.push_back(val.front());
}
val.pop_front();
}
2017-01-30 00:54:06 +01:00
ret.push_back('"');
return ret;
}
2015-07-11 20:14:12 +02:00
template<typename T, typename R>
2017-02-09 20:56:15 +01:00
static std::true_type test_tofmt(decltype(to_format(
2017-01-29 15:29:11 +01:00
std::declval<T const &>(), std::declval<R &>(),
2017-02-16 20:39:05 +01:00
std::declval<format_spec const &>()
)) *);
2015-07-11 20:14:12 +02:00
template<typename, typename>
2017-02-09 20:56:15 +01:00
static std::false_type test_tofmt(...);
2015-07-11 20:14:12 +02:00
template<typename T, typename R>
2017-02-16 20:39:05 +01:00
constexpr bool fmt_tofmt_test = decltype(test_tofmt<T, R>(0))::value;
2017-02-16 20:39:05 +01:00
struct write_spec: format_spec {
using format_spec::format_spec;
/* string base writer */
template<typename R>
void write_str(R &writer, bool escape, string_range val) const {
2015-07-04 20:19:49 +02:00
if (escape) {
write_str(writer, false, escape_fmt_str(val));
return;
}
2017-01-30 19:11:39 +01:00
size_t n = val.size();
if (this->precision()) {
n = this->precision();
}
this->write_spaces(writer, n, true);
range_put_all(writer, val.slice(0, n));
this->write_spaces(writer, n, false);
}
/* char values */
template<typename R>
void write_char(R &writer, bool escape, char val) const {
2015-07-04 20:19:49 +02:00
if (escape) {
2016-06-23 20:18:35 +02:00
char const *esc = escape_fmt_char(val, '\'');
if (esc) {
char buf[6];
buf[0] = '\'';
2017-01-30 19:11:39 +01:00
size_t elen = strlen(esc);
memcpy(buf + 1, esc, elen);
buf[elen + 1] = '\'';
write_val(writer, false, ostd::string_range{
buf, buf + elen + 2
});
return;
}
}
this->write_spaces(writer, 1 + escape * 2, true);
2015-07-04 20:19:49 +02:00
if (escape) {
writer.put('\'');
writer.put(val);
writer.put('\'');
} else {
writer.put(val);
}
this->write_spaces(writer, 1 + escape * 2, false);
}
/* floating point */
2017-02-09 20:56:15 +01:00
template<typename R, typename T, bool Long = std::is_same_v<T, ldouble>>
void write_float(R &writer, bool, T val) const {
char buf[16], rbuf[128];
char fmtspec[Long + 1];
2015-07-04 20:19:49 +02:00
fmtspec[Long] = this->spec();
byte specn = detail::fmt_specs[this->spec() - 65];
if (specn != 1 && specn != 7) {
throw format_error{"cannot format floats with the given spec"};
}
if (specn == 7) {
fmtspec[Long] = 'g';
}
if (Long) {
fmtspec[0] = 'L';
}
this->build_spec(iter(buf), fmtspec).put('\0');
2017-02-11 01:28:14 +01:00
int ret = snprintf(
rbuf, sizeof(rbuf), buf, this->width(),
this->has_precision() ? this->precision() : 6, val
);
2017-02-11 01:28:14 +01:00
if (ret < 0) {
/* typically unreachable, build_spec creates valid format */
throw format_error{"invalid float format"};
}
char *dbuf = nullptr;
2017-01-30 19:11:39 +01:00
if (size_t(ret) >= sizeof(rbuf)) {
/* this should typically never happen */
2016-07-02 05:56:23 +02:00
dbuf = new char[ret + 1];
ret = snprintf(
dbuf, ret + 1, buf, this->width(),
this->has_precision() ? this->precision() : 6, val
);
2017-02-11 01:28:14 +01:00
if (ret < 0) {
/* see above */
throw format_error{"invalid float format"};
}
range_put_all(writer, string_range{dbuf, dbuf + ret});
2016-07-02 05:56:23 +02:00
delete[] dbuf;
} else {
range_put_all(writer, string_range{rbuf, rbuf + ret});
}
}
2015-07-03 22:19:50 +02:00
template<typename R, typename T>
void write_val(R &writer, bool escape, T const &val) const {
/* stuff fhat can be custom-formatted goes first */
if constexpr(fmt_tofmt_test<T, noop_output_range<char>>) {
to_format(val, writer, *this);
return;
}
/* second best, we can convert to string slice */
2017-02-16 18:48:14 +01:00
if constexpr(std::is_constructible_v<string_range, T const &>) {
if (this->spec() != 's') {
throw format_error{"strings need the '%s' spec"};
}
write_str(writer, escape, val);
return;
}
/* bools, check if printing as string, otherwise convert to int */
if constexpr(std::is_same_v<T, bool>) {
if (this->spec() == 's') {
write_val(writer, ("false\0true") + (6 * val));
} else {
write_val(writer, int(val));
}
return;
2015-07-03 22:19:50 +02:00
}
/* character values */
if constexpr(std::is_same_v<T, char>) {
if (this->spec() != 's' && this->spec() != 'c') {
throw format_error{"cannot format chars with the given spec"};
}
write_char(writer, escape, val);
return;
}
/* pointers, write as pointer with %s and otherwise as unsigned...
* char pointers are handled by the string case above
*/
if constexpr(std::is_pointer_v<T>) {
2017-02-16 20:39:05 +01:00
format_spec sp{
(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;
}
/* integers */
if constexpr(std::is_integral_v<T>) {
if constexpr(std::is_signed_v<T>) {
/* signed integers */
using UT = std::make_unsigned_t<T>;
detail::write_u(
writer, this, val < 0,
(val < 0) ? static_cast<UT>(-val) : static_cast<UT>(val)
);
} else {
/* unsigned integers */
detail::write_u(writer, this, false, val);
}
return;
2015-07-03 22:55:35 +02:00
}
/* floats */
if constexpr(std::is_floating_point_v<T>) {
write_float(writer, escape, val);
return;
}
/* stuff that can be to_string'd, worst reliable case, allocates */
2017-02-16 20:39:05 +01:00
if constexpr(fmt_tostr_test<T>) {
if (this->spec() != 's') {
throw format_error{"custom objects need the '%s' spec"};
}
write_val(writer, false, ostd::to_string<T>{}(val));
return;
}
/* we ran out of options, failure */
throw format_error{"the value cannot be formatted"};
}
/* actual writer */
template<typename R, typename T, typename ...A>
void write_arg(
2017-01-30 19:11:39 +01:00
R &writer, size_t idx, T const &val, A const &...args
) const {
if (idx) {
if constexpr(!sizeof...(A)) {
throw format_error{"not enough format arguments"};
} else {
write_arg(writer, idx - 1, args...);
}
} else {
write_val(writer, this->p_nested_escape, val);
}
}
2015-07-04 03:18:21 +02:00
/* range writer */
template<typename R, typename T, typename ...A>
void write_range(
2017-02-16 18:48:14 +01:00
R &writer, size_t idx, bool expandval, string_range sep,
T const &val, A const &...args
) const {
if (idx) {
if constexpr(!sizeof...(A)) {
throw format_error{"not enough format arguments"};
} else {
write_range(writer, idx - 1, expandval, sep, args...);
}
} else {
detail::write_range(
writer, this, this->p_nested_escape, expandval, sep, val
);
}
2015-07-04 03:18:21 +02:00
}
};
2015-07-04 02:18:14 +02:00
template<typename R, typename ...A>
inline void format_impl(
2017-02-16 18:48:14 +01:00
R &writer, bool escape, string_range fmt, A const &...args
) {
size_t argidx = 1;
2017-02-16 20:39:05 +01:00
detail::write_spec spec(fmt, escape);
while (spec.read_until_spec(writer)) {
2017-01-30 19:11:39 +01:00
size_t argpos = spec.index();
2015-07-04 20:19:49 +02:00
if (spec.is_nested()) {
if (!argpos) {
argpos = argidx++;
}
2015-07-04 03:18:21 +02:00
/* FIXME: figure out a better way */
2017-02-16 20:39:05 +01:00
detail::write_spec nspec(spec.nested(), spec.nested_escape());
nspec.write_range(
writer, argpos - 1, (spec.flags() & FMT_FLAG_HASH),
spec.nested_sep(), args...
);
2015-07-04 03:18:21 +02:00
continue;
}
2015-07-04 02:18:14 +02:00
if (!argpos) {
argpos = argidx++;
2015-07-04 20:19:49 +02:00
if (spec.arg_width()) {
spec.set_width(argpos - 1, args...);
2015-07-04 02:18:14 +02:00
argpos = argidx++;
}
2015-07-04 20:19:49 +02:00
if (spec.arg_precision()) {
spec.set_precision(argpos - 1, args...);
2015-07-04 02:18:14 +02:00
argpos = argidx++;
}
} else {
2015-07-04 20:19:49 +02:00
bool argprec = spec.arg_precision();
2015-07-04 02:18:14 +02:00
if (argprec) {
if (argpos <= 1) {
throw format_error{"argument precision not given"};
}
spec.set_precision(argpos - 2, args...);
2015-07-04 02:18:14 +02:00
}
2015-07-04 20:19:49 +02:00
if (spec.arg_width()) {
2017-01-30 19:11:39 +01:00
if (argpos <= (size_t(argprec) + 1)) {
throw format_error{"argument width not given"};
}
spec.set_width(argpos - 2 - argprec, args...);
}
}
spec.write_arg(writer, argpos - 1, args...);
}
2015-07-01 03:22:42 +02:00
}
template<typename R>
inline void format_impl(R &writer, bool, string_range fmt) {
2017-02-16 20:39:05 +01:00
detail::write_spec spec(fmt, false);
if (spec.read_until_spec(writer)) {
throw format_error{"format spec without format arguments"};
}
}
2015-07-04 02:18:14 +02:00
} /* namespace detail */
template<typename R, typename ...A>
inline R &&format(R &&writer, string_range fmt, A const &...args) {
detail::format_impl(writer, false, fmt, 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 */
2017-02-16 20:39:05 +01:00
detail::write_spec const &wsp = static_cast<detail::write_spec const &>(fmt);
wsp.write_arg(writer, 0, val);
return std::forward<R>(writer);
2015-07-01 03:22:42 +02:00
}
2015-07-13 21:07:14 +02:00
} /* namespace ostd */
2015-07-01 03:22:42 +02:00
2016-02-07 22:17:15 +01:00
#endif