| // SPDX-License-Identifier: Apache-2.0 |
| // Copyright Apache Software Foundation 2019 |
| |
| /** @file |
| |
| Formatted output for BufferWriter. |
| */ |
| |
| #include <array> |
| #include <cctype> |
| #include <chrono> |
| #include <cmath> |
| #include <ctime> |
| #include <cmath> |
| #include <sys/param.h> |
| #include <unistd.h> |
| |
| #include "swoc/BufferWriter.h" |
| #include "swoc/bwf_base.h" |
| #include "swoc/bwf_ex.h" |
| #include "swoc/swoc_meta.h" |
| #include "swoc/DiscreteRange.h" |
| |
| using namespace std::literals; |
| using namespace swoc::literals; |
| |
| |
| using swoc::svto_radix; |
| |
| namespace swoc { inline namespace SWOC_VERSION_NS { |
| |
| namespace bwf { |
| ExternalNames& Global_Names() { |
| static swoc::bwf::ExternalNames Global_Names; |
| return Global_Names; |
| } |
| |
| const Spec Spec::DEFAULT; |
| |
| #pragma GCC diagnostic ignored "-Wchar-subscripts" |
| |
| Spec::Property::Property() { |
| memset(_data, 0, sizeof(_data)); |
| _data['b'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; |
| _data['B'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; |
| _data['d'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; |
| _data['g'] = TYPE_CHAR; |
| _data['o'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; |
| _data['p'] = TYPE_CHAR; |
| _data['P'] = TYPE_CHAR | UPPER_TYPE_CHAR; |
| _data['s'] = TYPE_CHAR; |
| _data['S'] = TYPE_CHAR | UPPER_TYPE_CHAR; |
| _data['x'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; |
| _data['X'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; |
| |
| _data[SIGN_NEVER] = SIGN_CHAR; |
| _data[SIGN_NEG] = SIGN_CHAR; |
| _data[SIGN_ALWAYS] = SIGN_CHAR; |
| |
| _data['<'] = static_cast<uint8_t>(Spec::Align::LEFT); |
| _data['>'] = static_cast<uint8_t>(Spec::Align::RIGHT); |
| _data['^'] = static_cast<uint8_t>(Spec::Align::CENTER); |
| _data['='] = static_cast<uint8_t>(Spec::Align::SIGN); |
| } |
| |
| Spec::Spec(const TextView &fmt) { |
| this->parse(fmt); |
| } |
| |
| /// Parse a format specification. |
| bool |
| Spec::parse(TextView fmt) { |
| TextView num; // temporary for number parsing. |
| |
| _name = fmt.take_prefix_at(':'); |
| // if it's parsable as a number, treat it as an index. |
| num = _name; |
| auto n = svto_radix<10>(num); |
| if (num.empty()) { |
| _idx = static_cast<decltype(_idx)>(n); |
| } |
| |
| if (fmt.size()) { |
| TextView sz = fmt.take_prefix_at(':'); // the format specifier. |
| _ext = fmt; // anything past the second ':' is the extension. |
| if (sz.size()) { |
| // fill and alignment |
| if ('%' == *sz) { // enable URI encoding of the fill character so |
| // metasyntactic chars can be used if needed. |
| if (sz.size() < 4) { |
| throw std::invalid_argument("Fill URI encoding without 2 hex characters and align mark"); |
| } |
| if (Align::NONE == (_align = align_of(sz[3]))) { |
| throw std::invalid_argument("Fill URI without alignment mark"); |
| } |
| char d1 = sz[1], d0 = sz[2]; |
| if (!isxdigit(d0) || !isxdigit(d1)) { |
| throw std::invalid_argument("URI encoding with non-hex characters"); |
| } |
| _fill = isdigit(d0) ? d0 - '0' : tolower(d0) - 'a' + 10; |
| _fill += (isdigit(d1) ? d1 - '0' : tolower(d1) - 'a' + 10) << 4; |
| sz += 4; |
| } else if (sz.size() > 1 && Align::NONE != (_align = align_of(sz[1]))) { |
| _fill = *sz; |
| sz += 2; |
| } else if (Align::NONE != (_align = align_of(*sz))) { |
| ++sz; |
| } |
| if (!sz.size()) { |
| return true; |
| } |
| // sign |
| if (is_sign(*sz)) { |
| _sign = *sz; |
| if (!(++sz).size()) { |
| return true; |
| } |
| } |
| // radix prefix |
| if ('#' == *sz) { |
| _radix_lead_p = true; |
| if (!(++sz).size()) { |
| return true; |
| } |
| } |
| // 0 fill for integers |
| if ('0' == *sz) { |
| if (Align::NONE == _align) { |
| _align = Align::SIGN; |
| } |
| _fill = '0'; |
| ++sz; |
| } |
| num = sz; |
| n = svto_radix<10>(num); |
| if (num.size() < sz.size()) { |
| _min = static_cast<decltype(_min)>(n); |
| sz = num; |
| if (!sz.size()) { |
| return true; |
| } |
| } |
| // precision |
| if ('.' == *sz) { |
| num = ++sz; |
| n = svto_radix<10>(num); |
| if (num.size() < sz.size()) { |
| _prec = static_cast<decltype(_prec)>(n); |
| sz = num; |
| if (!sz.size()) { |
| return true; |
| } |
| } else { |
| throw std::invalid_argument("Precision mark without precision"); |
| } |
| } |
| // style (type). Hex, octal, etc. |
| if (is_type(*sz)) { |
| _type = *sz; |
| if (!(++sz).size()) { |
| return true; |
| } |
| } |
| // maximum width |
| if (',' == *sz) { |
| num = ++sz; |
| n = svto_radix<10>(num); |
| if (num.size() < sz.size()) { |
| _max = static_cast<decltype(_max)>(n); |
| sz = num; |
| if (!sz.size()) { |
| return true; |
| } |
| } else { |
| throw std::invalid_argument("Maximum width mark without width"); |
| } |
| // Can only have a type indicator here if there was a max width. |
| if (is_type(*sz)) { |
| _type = *sz; |
| if (!(++sz).size()) { |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| /// Parse out the next literal and/or format specifier from the format string. |
| /// Pass the results back in @a literal and @a specifier as appropriate. |
| /// Update @a fmt to strip the parsed text. |
| /// @return @c true if a specifier was parsed, @c false if not. |
| bool |
| Format::TextViewExtractor::parse(TextView &fmt, std::string_view &literal, std::string_view &specifier) { |
| TextView::size_type off = 0; |
| |
| // Check for brace delimiters. |
| off = fmt.find_if([](char c) { return '{' == c || '}' == c; }); |
| if (off == TextView::npos) { |
| // not found, it's a literal, ship it. |
| literal = fmt; |
| fmt.remove_prefix(literal.size()); |
| return false; |
| } |
| |
| // Processing for braces that don't enclose specifiers. |
| if (fmt.size() > off + 1) { |
| char c1 = fmt[off]; |
| char c2 = fmt[off + 1]; |
| if (c1 == c2) { |
| // double braces count as literals, but must tweak to output only 1 brace. |
| literal = fmt.take_prefix(off + 1); |
| return false; |
| } else if ('}' == c1) { |
| throw std::invalid_argument("Unopened } in format string."); |
| } else { |
| literal = std::string_view{fmt.data(), off}; |
| fmt.remove_prefix(off + 1); |
| } |
| } else { |
| throw std::invalid_argument("Invalid trailing character in format string."); |
| } |
| |
| if (fmt.size()) { |
| // Need to be careful, because an empty format is OK and it's hard to tell |
| // if take_prefix_at failed to find the delimiter or found it as the first |
| // byte. |
| off = fmt.find('}'); |
| if (off == TextView::npos) { |
| throw std::invalid_argument("BWFormat: Unclosed { in format string"); |
| } |
| specifier = fmt.take_prefix(off); |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| Format::TextViewExtractor::operator()(std::string_view &literal_v, Spec &spec) { |
| if (!_fmt.empty()) { |
| std::string_view spec_v; |
| if (parse(_fmt, literal_v, spec_v)) { |
| return spec.parse(spec_v); |
| } |
| } |
| return false; |
| } |
| |
| bool |
| Format::FormatExtractor::operator()(std::string_view &literal_v, swoc::bwf::Spec &spec) { |
| literal_v = {}; |
| if (_idx < int(_fmt.size()) && _fmt[_idx]._type == Spec::LITERAL_TYPE) { |
| literal_v = _fmt[_idx++]._ext; |
| } |
| if (_idx < int(_fmt.size()) && _fmt[_idx]._type != Spec::LITERAL_TYPE) { |
| spec = _fmt[_idx++]; |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n) { |
| static const Format fmt{"{{BAD_ARG_INDEX:{} of {}}}"sv}; |
| w.print(fmt, i, n); |
| } |
| |
| /** This performs generic alignment operations. |
| |
| If a formatter specialization performs this operation instead, that should |
| result in output that is at least @a spec._min characters wide, which will |
| cause this function to make no further adjustments. |
| */ |
| void |
| Adjust_Alignment(BufferWriter &aux, Spec const &spec) { |
| size_t extent = aux.extent(); |
| size_t min = spec._min; |
| if (extent < min) { |
| size_t delta = min - extent; |
| size_t left_delta = 0, right_delta = delta; // left justify values |
| if (Spec::Align::RIGHT == spec._align) { |
| left_delta = delta; |
| right_delta = 0; |
| } else if (Spec::Align::CENTER == spec._align) { |
| left_delta = delta / 2; |
| right_delta = (delta + 1) / 2; |
| } |
| if (left_delta > 0) { |
| size_t work_area = extent + left_delta; |
| aux.commit(left_delta); // cover work area. |
| aux.copy(left_delta, 0, extent); // move to create space for left fill. |
| aux.discard(work_area); // roll back to write the left fill. |
| for (int i = left_delta; i > 0; --i) { |
| aux.write(spec._fill); |
| } |
| aux.commit(extent); |
| } |
| for (int i = right_delta; i > 0; --i) { |
| aux.write(spec._fill); |
| } |
| |
| } else { |
| size_t max = spec._max; |
| if (max < extent) { |
| aux.discard(extent - max); |
| } |
| } |
| } |
| |
| // Conversions from remainder to character, in upper and lower case versions. |
| // Really only useful for hexadecimal currently. |
| namespace { |
| char UPPER_DIGITS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| char LOWER_DIGITS[] = "0123456789abcdefghijklmnopqrstuvwxyz"; |
| static const std::array<uint64_t, 11> POWERS_OF_TEN = { |
| {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000} |
| }; |
| } // namespace |
| |
| /// Templated radix based conversions. Only a small number of radix are |
| /// supported and providing a template minimizes cut and paste code while also |
| /// enabling compiler optimizations (e.g. for power of 2 radix the modulo / |
| /// divide become bit operations). |
| template <size_t RADIX> |
| size_t |
| To_Radix(uintmax_t n, char *buff, size_t width, char *digits) { |
| static_assert(1 < RADIX && RADIX <= 36, "RADIX must be in the range 2..36"); |
| char *out = buff + width; |
| if (n) { |
| while (n) { |
| *--out = digits[n % RADIX]; |
| n /= RADIX; |
| } |
| } else { |
| *--out = '0'; |
| } |
| return (buff + width) - out; |
| } |
| |
| /** Output a string with a specified alignment. |
| * |
| * @tparam F Output functor. |
| * @param w Output buffer. |
| * @param f Functor that generates output. |
| * @param align Alignment. |
| * @param width Field width. |
| * @param fill Fill character. |
| * @param neg Character to use for negative (null char means do not output). |
| */ |
| template <typename F> |
| void |
| Write_Aligned(BufferWriter &w, F const &f, Spec::Align align, int width, char fill, char neg) { |
| switch (align) { |
| case Spec::Align::LEFT: |
| if (neg) { |
| w.write(neg); |
| } |
| f(); |
| while (width-- > 0) { |
| w.write(fill); |
| } |
| break; |
| case Spec::Align::RIGHT: |
| while (width-- > 0) { |
| w.write(fill); |
| } |
| if (neg) { |
| w.write(neg); |
| } |
| f(); |
| break; |
| case Spec::Align::CENTER: |
| for (int i = width / 2; i > 0; --i) { |
| w.write(fill); |
| } |
| if (neg) { |
| w.write(neg); |
| } |
| f(); |
| for (int i = (width + 1) / 2; i > 0; --i) { |
| w.write(fill); |
| } |
| break; |
| case Spec::Align::SIGN: |
| if (neg) { |
| w.write(neg); |
| } |
| while (width-- > 0) { |
| w.write(fill); |
| } |
| f(); |
| break; |
| default: |
| if (neg) { |
| w.write(neg); |
| } |
| f(); |
| break; |
| } |
| } |
| |
| BufferWriter & |
| Format_Integer(BufferWriter &w, Spec const &spec, uintmax_t i, bool neg_p) { |
| size_t n = 0; |
| int width = static_cast<int>(spec._min); // amount left to fill. |
| char neg = 0; |
| char prefix1 = spec._radix_lead_p ? '0' : 0; |
| char prefix2 = 0; |
| char buff[std::numeric_limits<uintmax_t>::digits + 1]; |
| |
| if (spec._sign != Spec::SIGN_NEVER) { |
| if (neg_p) { |
| neg = '-'; |
| } else if (spec._sign == Spec::SIGN_ALWAYS) { |
| neg = spec._sign; |
| } |
| } |
| |
| switch (spec._type) { |
| case 'x': |
| prefix2 = 'x'; |
| n = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); |
| break; |
| case 'X': |
| prefix2 = 'X'; |
| n = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::UPPER_DIGITS); |
| break; |
| case 'b': |
| prefix2 = 'b'; |
| n = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); |
| break; |
| case 'B': |
| prefix2 = 'B'; |
| n = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::UPPER_DIGITS); |
| break; |
| case 'o': |
| n = bwf::To_Radix<8>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); |
| break; |
| default: |
| prefix1 = 0; |
| n = bwf::To_Radix<10>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); |
| break; |
| } |
| // Clip fill width by stuff that's already committed to be written. |
| if (neg) { |
| --width; |
| } |
| if (prefix1) { |
| --width; |
| if (prefix2) { |
| --width; |
| } |
| } |
| width -= static_cast<int>(n); |
| std::string_view digits{buff + sizeof(buff) - n, n}; |
| |
| if (spec._align == Spec::Align::SIGN) { // custom for signed case because |
| // prefix and digits are seperated. |
| if (neg) { |
| w.write(neg); |
| } |
| if (prefix1) { |
| w.write(prefix1); |
| if (prefix2) { |
| w.write(prefix2); |
| } |
| } |
| while (width-- > 0) { |
| w.write(spec._fill); |
| } |
| w.write(digits); |
| } else { // use generic Write_Aligned |
| Write_Aligned( |
| w, |
| [&]() { |
| if (prefix1) { |
| w.write(prefix1); |
| if (prefix2) { |
| w.write(prefix2); |
| } |
| } |
| w.write(digits); |
| }, |
| spec._align, width, spec._fill, neg); |
| } |
| return w; |
| } |
| |
| /// Format for floating point values. Seperates floating point into a whole |
| /// number and a fraction. The fraction is converted into an unsigned integer |
| /// based on the specified precision, spec._prec. ie. 3.1415 with precision two |
| /// is seperated into two unsigned integers 3 and 14. The different pieces are |
| /// assembled and placed into the BufferWriter. The default is two decimal |
| /// places. ie. X.XX. The value is always written in base 10. |
| /// |
| /// format: whole.fraction |
| /// or: left.right |
| BufferWriter & |
| Format_Float(BufferWriter &w, Spec const &spec, double f, bool negative_p) { |
| static const std::string_view infinity_bwf{"Inf"}; |
| static const std::string_view nan_bwf{"NaN"}; |
| static const std::string_view zero_bwf{"0"}; |
| static const std::string_view subnormal_bwf{"subnormal"}; |
| static const std::string_view unknown_bwf{"unknown float"}; |
| |
| // Handle floating values that are not normal |
| if (!std::isnormal(f)) { |
| std::string_view unnormal; |
| switch (std::fpclassify(f)) { |
| case FP_INFINITE: |
| unnormal = infinity_bwf; |
| break; |
| case FP_NAN: |
| unnormal = nan_bwf; |
| break; |
| case FP_ZERO: |
| unnormal = zero_bwf; |
| break; |
| case FP_SUBNORMAL: |
| unnormal = subnormal_bwf; |
| break; |
| default: |
| unnormal = unknown_bwf; |
| } |
| |
| w.write(unnormal); |
| return w; |
| } |
| |
| auto whole_part = static_cast<uint64_t>(f); |
| if (whole_part == f || spec._prec == 0) { // integral |
| return Format_Integer(w, spec, whole_part, negative_p); |
| } |
| |
| static constexpr char dec = '.'; |
| double frac = NAN; |
| size_t l = 0; |
| size_t r = 0; |
| char whole[std::numeric_limits<double>::digits10 + 1]; |
| char fraction[std::numeric_limits<double>::digits10 + 1]; |
| char neg = 0; |
| int width = static_cast<int>(spec._min); // amount left to fill. |
| unsigned int precision = (spec._prec == Spec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2 |
| |
| frac = f - whole_part; // split the number |
| |
| if (negative_p) { |
| neg = '-'; |
| } else if (spec._sign != '-') { |
| neg = spec._sign; |
| } |
| |
| // Shift the floating point based on the precision. Used to convert |
| // trailing fraction into an integer value. |
| uint64_t shift = 0; |
| if (precision < POWERS_OF_TEN.size()) { |
| shift = POWERS_OF_TEN[precision]; |
| } else { // not precomputed. |
| shift = POWERS_OF_TEN.back(); |
| for (precision -= (POWERS_OF_TEN.size() - 1); precision > 0; --precision) { |
| shift *= 10; |
| } |
| } |
| |
| auto frac_part = static_cast<uint64_t>(frac * shift + 0.5 /* rounding */); |
| |
| l = bwf::To_Radix<10>(whole_part, whole, sizeof(whole), bwf::LOWER_DIGITS); |
| r = bwf::To_Radix<10>(frac_part, fraction, sizeof(fraction), bwf::LOWER_DIGITS); |
| |
| // Clip fill width |
| if (neg) { |
| --width; |
| } |
| width -= static_cast<int>(l); |
| --width; // '.' |
| width -= static_cast<int>(r); |
| |
| std::string_view whole_digits{whole + sizeof(whole) - l, l}; |
| std::string_view frac_digits{fraction + sizeof(fraction) - r, r}; |
| |
| Write_Aligned( |
| w, |
| [&]() { |
| w.write(whole_digits); |
| w.write(dec); |
| w.write(frac_digits); |
| }, |
| spec._align, width, spec._fill, neg); |
| |
| return w; |
| } |
| |
| void |
| Format_As_Hex(BufferWriter &w, std::string_view view, const char *digits) { |
| const char *ptr = view.data(); |
| for (auto n = view.size(); n > 0; --n) { |
| char c = *ptr++; |
| w.write(digits[(c >> 4) & 0xF]); |
| w.write(digits[c & 0xf]); |
| } |
| } |
| |
| /// Preparse format string for later use. |
| Format::Format(TextView fmt) { |
| Spec lit_spec; |
| int arg_idx = 0; |
| auto ex{bind(fmt)}; |
| std::string_view literal_v; |
| |
| lit_spec._type = Spec::LITERAL_TYPE; |
| |
| while (ex) { |
| Spec spec; |
| bool spec_p = ex(literal_v, spec); |
| |
| if (literal_v.size()) { |
| lit_spec._ext = literal_v; |
| _items.emplace_back(lit_spec); |
| } |
| |
| if (spec_p) { |
| if (spec._name.size() == 0) { // no name provided, use implicit index. |
| spec._idx = arg_idx++; |
| } |
| if (spec._idx >= 0) { |
| ++arg_idx; |
| } |
| _items.emplace_back(spec); |
| } |
| } |
| } |
| |
| bool |
| Format::is_literal() const { |
| for (auto const &spec : _items) { |
| if (Spec::LITERAL_TYPE != spec._type) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| NameBinding::~NameBinding() = default; |
| |
| } // namespace bwf |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv) { |
| auto width = int(spec._min); // amount to fill. |
| if (spec._prec > 0) { |
| sv = sv.substr(0, spec._prec); |
| } |
| if ('x' == spec._type || 'X' == spec._type) { |
| return bwformat(w, spec, bwf::HexDump(sv.data(), sv.size())); |
| } else if ('s' == spec._type) { |
| bwformat(w, spec, transform_view_of(&tolower, sv)); |
| } else if ('S' == spec._type) { |
| bwformat(w, spec, transform_view_of(&toupper, sv)); |
| } else { |
| width -= sv.size(); |
| bwf::Write_Aligned( |
| w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0); |
| } |
| return w; |
| } |
| |
| // Generic pointer formatting |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr) { |
| using namespace swoc::literals; |
| bwf::Spec ptr_spec{spec}; |
| ptr_spec._radix_lead_p = true; |
| |
| if (ptr == nullptr) { |
| if (spec._type == 's' || spec._type == 'S') { |
| ptr_spec._type = bwf::Spec::DEFAULT_TYPE; |
| ptr_spec._ext = ""_sv; // clear any extension. |
| return bwformat(w, spec, spec._type == 's' ? "null"_sv : "NULL"_sv); |
| } else if (spec._type == bwf::Spec::DEFAULT_TYPE) { |
| return w; // print nothing if there is no format character override. |
| } |
| } |
| |
| if (ptr_spec._type == bwf::Spec::DEFAULT_TYPE || ptr_spec._type == 'p') { |
| ptr_spec._type = 'x'; // if default or 'p;, switch to lower hex. |
| } else if (ptr_spec._type == 'P') { |
| ptr_spec._type = 'X'; // P means upper hex, overriding other specializations. |
| } |
| return bwf::Format_Integer(w, ptr_spec, reinterpret_cast<intptr_t>(ptr), false); |
| } |
| // doc end |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::HexDump const &hex) { |
| char fmt_type = spec._type; |
| const char *digits = bwf::UPPER_DIGITS; |
| |
| if ('X' != fmt_type) { |
| fmt_type = 'x'; |
| digits = bwf::LOWER_DIGITS; |
| } |
| |
| int width = int(spec._min) - hex._view.size() * 2; // amount left to fill. |
| if (spec._radix_lead_p) { |
| w.write('0'); |
| w.write(fmt_type); |
| width -= 2; |
| } |
| bwf::Write_Aligned( |
| w, [&w, &hex, digits]() { bwf::Format_As_Hex(w, hex._view, digits); }, spec._align, width, spec._fill, 0); |
| return w; |
| } |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void const> const &span) { |
| if ('x' == spec._type || 'X' == spec._type) { |
| const char *digits = ('X' == spec._type) ? bwf::UPPER_DIGITS : bwf::LOWER_DIGITS; |
| size_t block = spec._prec > 0 ? spec._prec : span.size(); |
| TextView view{span.rebind<char const>()}; |
| bool space_p = false; |
| while (view) { |
| if (space_p) { |
| w.write(' '); |
| } |
| space_p = true; |
| if (spec._radix_lead_p) { |
| w.write('0').write(digits[33]); |
| } |
| bwf::Format_As_Hex(w, view.prefix(block), digits); |
| view.remove_prefix(block); |
| } |
| } else { |
| static const bwf::Format default_fmt{"{:#x}@{:p}"}; |
| w.print(default_fmt, span.size(), span.data()); |
| } |
| return w; |
| } |
| |
| std::ostream & |
| FixedBufferWriter::operator>>(std::ostream &s) const { |
| return s << this->view(); |
| } |
| |
| namespace { |
| // Hand rolled, might not be totally compliant everywhere, but probably close |
| // enough. The long string will be locally accurate. Clang requires the double |
| // braces. Why, Turing only knows. |
| static const std::array<std::string_view, 134> ERRNO_SHORT_NAME = { |
| { |
| "SUCCESS", "EPERM", |
| "ENOENT", "ESRCH", |
| "EINTR", "EIO", |
| "ENXIO", "E2BIG ", |
| "ENOEXEC", "EBADF", |
| "ECHILD", "EAGAIN", |
| "ENOMEM", "EACCES", |
| "EFAULT", "ENOTBLK", |
| "EBUSY", "EEXIST", |
| "EXDEV", "ENODEV", |
| "ENOTDIR", "EISDIR", |
| "EINVAL", "ENFILE", |
| "EMFILE", "ENOTTY", |
| "ETXTBSY", "EFBIG", |
| "ENOSPC", "ESPIPE", |
| "EROFS", "EMLINK", |
| "EPIPE", "EDOM", |
| "ERANGE", "EDEADLK", |
| "ENAMETOOLONG", "ENOLCK", |
| "ENOSYS", "ENOTEMPTY", |
| "ELOOP", "EWOULDBLOCK", |
| "ENOMSG", "EIDRM", |
| "ECHRNG", "EL2NSYNC", |
| "EL3HLT", "EL3RST", |
| "ELNRNG", "EUNATCH", |
| "ENOCSI", "EL2HTL", |
| "EBADE", "EBADR", |
| "EXFULL", "ENOANO", |
| "EBADRQC", "EBADSLT", |
| "EDEADLOCK", "EBFONT", |
| "ENOSTR", "ENODATA", |
| "ETIME", "ENOSR", |
| "ENONET", "ENOPKG", |
| "EREMOTE", "ENOLINK", |
| "EADV", "ESRMNT", |
| "ECOMM", "EPROTO", |
| "EMULTIHOP", "EDOTDOT", |
| "EBADMSG", "EOVERFLOW", |
| "ENOTUNIQ", "EBADFD", |
| "EREMCHG", "ELIBACC", |
| "ELIBBAD", "ELIBSCN", |
| "ELIBMAX", "ELIBEXEC", |
| "EILSEQ", "ERESTART", |
| "ESTRPIPE", "EUSERS", |
| "ENOTSOCK", "EDESTADDRREQ", |
| "EMSGSIZE", "EPROTOTYPE", |
| "ENOPROTOOPT", "EPROTONOSUPPORT", |
| "ESOCKTNOSUPPORT", "EOPNOTSUPP", |
| "EPFNOSUPPORT", "EAFNOSUPPORT", |
| "EADDRINUSE", "EADDRNOTAVAIL", |
| "ENETDOWN", "ENETUNREACH", |
| "ENETRESET", "ECONNABORTED", |
| "ECONNRESET", "ENOBUFS", |
| "EISCONN", "ENOTCONN", |
| "ESHUTDOWN", "ETOOMANYREFS", |
| "ETIMEDOUT", "ECONNREFUSED", |
| "EHOSTDOWN", "EHOSTUNREACH", |
| "EALREADY", "EINPROGRESS", |
| "ESTALE", "EUCLEAN", |
| "ENOTNAM", "ENAVAIL", |
| "EISNAM", "EREMOTEIO", |
| "EDQUOT", "ENOMEDIUM", |
| "EMEDIUMTYPE", "ECANCELED", |
| "ENOKEY", "EKEYEXPIRED", |
| "EKEYREVOKED", "EKEYREJECTED", |
| "EOWNERDEAD", "ENOTRECOVERABLE", |
| "ERFKILL", "EHWPOISON", |
| } |
| }; |
| static constexpr DiscreteRange<unsigned> ERRNO_RANGE{0, ERRNO_SHORT_NAME.size() - 1}; |
| // This provides convenient safe access to the errno short name array. |
| auto errno_short_name = [](unsigned n) { return ERRNO_RANGE.contains(n) ? ERRNO_SHORT_NAME[n] : "Unknown"sv; }; |
| } // namespace |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e) { |
| static const bwf::Format number_fmt{"[{}]"sv}; // numeric value format. |
| |
| if (spec.has_numeric_type()) { // if numeric type, print just the numeric part |
| w.print(number_fmt, e._e); |
| } else { |
| TextView ext{spec._ext}; |
| bool short_p = false; |
| if (ext.empty() || ext.npos != ext.find('s')) { |
| w.write(errno_short_name(e._e)); |
| short_p = true; |
| } |
| if (ext.empty() || ext.npos != ext.find('l')) { |
| if (short_p) { |
| w.write(": "); |
| } |
| w.write(TextView(strerror(e._e), TextView::npos)); |
| } |
| if (spec._type != 's' && spec._type != 'S') { |
| w.write(' '); |
| w.print(number_fmt, e._e); |
| } |
| } |
| return w; |
| } |
| |
| bwf::Date::Date(std::string_view fmt) : _epoch(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())), _fmt(fmt) {} |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &date) { |
| if (spec.has_numeric_type()) { |
| bwformat(w, spec, date._epoch); |
| } else { |
| struct tm t {}; |
| auto r = w.remaining(); |
| size_t n{0}; |
| // Verify @a fmt is null terminated, even outside the bounds of the view. |
| if (date._fmt.data()[date._fmt.size() - 1] != 0 && date._fmt.data()[date._fmt.size()] != 0) { |
| throw(std::invalid_argument{"BWF Date String is not null terminated."}); |
| } |
| // Get the time, GMT or local if specified. |
| if (spec._ext == "local"sv) { |
| localtime_r(&date._epoch, &t); |
| } else { |
| gmtime_r(&date._epoch, &t); |
| } |
| // Try a direct write, faster if it works. |
| if (r > 0) { |
| n = strftime(w.aux_data(), r, date._fmt.data(), &t); |
| } |
| if (n > 0) { |
| w.commit(n); |
| } else { |
| // Direct write didn't work. Unfortunately need to write to a temporary |
| // buffer or the sizing isn't correct if @a w is clipped because @c |
| // strftime returns 0 if the buffer isn't large enough. |
| char buff[256]; // hope for the best - no real way to resize appropriately on failure. |
| n = strftime(buff, sizeof(buff), date._fmt.data(), &t); |
| w.write(buff, n); |
| } |
| } |
| return w; |
| } |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Pattern const &pattern) { |
| if (!pattern._text.empty()) { // If there's no text, no point in looping. |
| auto limit = std::min<size_t>(spec._max, pattern._text.size() * pattern._n); |
| decltype(limit) n = 0; |
| while (n < limit) { |
| w.write(pattern._text); |
| n += pattern._text.size(); |
| } |
| } |
| return w; |
| } |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, std::error_code const &ec) { |
| static const auto G_CAT = &std::generic_category(); |
| static const auto S_CAT = &std::system_category(); |
| |
| // This provides convenient safe access to the errno short name array. |
| static const swoc::bwf::Format number_fmt{"[{}]"_sv}; // numeric value format. |
| if (spec.has_numeric_type()) { // if numeric type, print just the numeric part. |
| bwformat(w, spec, ec.value()); |
| } else { |
| if ((&ec.category() == G_CAT || &ec.category() == S_CAT) && swoc::ERRNO_RANGE.contains(ec.value())) { |
| bwformat(w, spec, swoc::ERRNO_SHORT_NAME[ec.value()]); |
| } else { |
| w.write(ec.message()); |
| } |
| if (spec._type != 's' && spec._type != 'S') { |
| w.write(' ').write('[').format(spec, ec.value()).write(']'); |
| } |
| } |
| return w; |
| } |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::UnHex const &obj) { |
| auto span{obj._span}; |
| size_t limit = spec._max; |
| while (span.size() >= 2 && limit--) { |
| auto b = svto_radix<16>(span.clip_prefix(2).rebind<char const>()); |
| w.write(b); |
| } |
| return w; |
| } |
| |
| }} // namespace swoc::SWOC_VERSION_NS |
| |
| namespace std { |
| ostream & |
| operator<<(ostream &s, swoc::FixedBufferWriter &w) { |
| return s << w.view(); |
| } |
| |
| } // namespace std |