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