| /** @file |
| |
| Formatted output for BufferWriter. |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #include "tscore/BufferWriter.h" |
| #include "tscore/bwf_std_format.h" |
| #include "tscore/ink_thread.h" |
| #include <unistd.h> |
| #include <sys/param.h> |
| #include <cctype> |
| #include <ctime> |
| #include <cmath> |
| #include <cmath> |
| #include <array> |
| #include <chrono> |
| |
| using namespace std::literals; |
| |
| namespace |
| { |
| // Customized version of string to int. Using this instead of the general @c svtoi function |
| // made @c bwprint performance test run in < 30% of the time, changing it from about 2.5 |
| // times slower than snprintf to the same speed. This version handles only positive integers |
| // in decimal. |
| inline int |
| tv_to_positive_decimal(ts::TextView src, ts::TextView *out) |
| { |
| int zret = 0; |
| |
| if (out) { |
| out->clear(); |
| } |
| src.ltrim_if(&isspace); |
| if (src.size()) { |
| const char *start = src.data(); |
| const char *limit = start + src.size(); |
| while (start < limit && ('0' <= *start && *start <= '9')) { |
| zret = zret * 10 + *start - '0'; |
| ++start; |
| } |
| if (out && (start > src.data())) { |
| out->assign(src.data(), start); |
| } |
| } |
| return zret; |
| } |
| } // namespace |
| |
| namespace ts |
| { |
| const BWFSpec BWFSpec::DEFAULT; |
| |
| const BWFSpec::Property BWFSpec::_prop; |
| |
| #pragma GCC diagnostic ignored "-Wchar-subscripts" |
| BWFSpec::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_CHAR; |
| _data['-'] = SIGN_CHAR; |
| _data['+'] = SIGN_CHAR; |
| |
| _data['<'] = static_cast<uint8_t>(BWFSpec::Align::LEFT); |
| _data['>'] = static_cast<uint8_t>(BWFSpec::Align::RIGHT); |
| _data['^'] = static_cast<uint8_t>(BWFSpec::Align::CENTER); |
| _data['='] = static_cast<uint8_t>(BWFSpec::Align::SIGN); |
| } |
| |
| /// Parse a format specification. |
| BWFSpec::BWFSpec(TextView fmt) |
| { |
| TextView num; // temporary for number parsing. |
| intmax_t n; |
| |
| _name = fmt.take_prefix_at(':'); |
| // if it's parsable as a number, treat it as an index. |
| n = tv_to_positive_decimal(_name, &num); |
| if (num.size()) { |
| _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; |
| } |
| // sign |
| if (is_sign(*sz)) { |
| _sign = *sz; |
| if (!(++sz).size()) { |
| return; |
| } |
| } |
| // radix prefix |
| if ('#' == *sz) { |
| _radix_lead_p = true; |
| if (!(++sz).size()) { |
| return; |
| } |
| } |
| // 0 fill for integers |
| if ('0' == *sz) { |
| if (Align::NONE == _align) { |
| _align = Align::SIGN; |
| } |
| _fill = '0'; |
| ++sz; |
| } |
| n = tv_to_positive_decimal(sz, &num); |
| if (num.size()) { |
| _min = static_cast<decltype(_min)>(n); |
| sz.remove_prefix(num.size()); |
| if (!sz.size()) { |
| return; |
| } |
| } |
| // precision |
| if ('.' == *sz) { |
| n = tv_to_positive_decimal(++sz, &num); |
| if (num.size()) { |
| _prec = static_cast<decltype(_prec)>(n); |
| sz.remove_prefix(num.size()); |
| if (!sz.size()) { |
| return; |
| } |
| } else { |
| throw std::invalid_argument("Precision mark without precision"); |
| } |
| } |
| // style (type). Hex, octal, etc. |
| if (is_type(*sz)) { |
| _type = *sz; |
| if (!(++sz).size()) { |
| return; |
| } |
| } |
| // maximum width |
| if (',' == *sz) { |
| n = tv_to_positive_decimal(++sz, &num); |
| if (num.size()) { |
| _max = static_cast<decltype(_max)>(n); |
| sz.remove_prefix(num.size()); |
| if (!sz.size()) { |
| return; |
| } |
| } 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; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| namespace bw_fmt |
| { |
| GlobalTable BWF_GLOBAL_TABLE; |
| |
| void |
| Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n) |
| { |
| static const BWFormat 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 |
| Do_Alignment(BWFSpec const &spec, BufferWriter &w, BufferWriter &lw) |
| { |
| size_t extent = lw.extent(); |
| size_t min = spec._min; |
| size_t size = lw.size(); |
| if (extent < min) { |
| size_t delta = min - extent; |
| char *base = w.auxBuffer(); // should be first byte of @a lw e.g. lw.data() - avoid const_cast. |
| char *limit = base + lw.capacity(); // first invalid byte. |
| char *dst; // used to track memory operation targest; |
| char *last; // track limit of memory operation. |
| size_t d2; |
| switch (spec._align) { |
| case BWFSpec::Align::RIGHT: |
| dst = base + delta; // move existing content to here. |
| if (dst < limit) { |
| last = dst + size; // amount of data to move. |
| if (last > limit) { |
| last = limit; |
| } |
| std::memmove(dst, base, last - dst); |
| } |
| dst = base; |
| last = base + delta; |
| if (last > limit) { |
| last = limit; |
| } |
| while (dst < last) { |
| *dst++ = spec._fill; |
| } |
| break; |
| case BWFSpec::Align::CENTER: |
| d2 = (delta + 1) / 2; // always > 0 because min > extent |
| // Move the original content right to make space to fill on the left. |
| dst = base + d2; // move existing content to here. |
| if (dst < limit) { |
| last = dst + size; // amount of data to move. |
| if (last > limit) { |
| last = limit; |
| } |
| std::memmove(dst, base, last - dst); // move content. |
| } |
| // Left fill. |
| dst = base; |
| last = base + d2; |
| if (last > limit) { |
| last = limit; |
| } |
| while (dst < last) { |
| *dst++ = spec._fill; |
| } |
| // Right fill. |
| dst += size; |
| last = dst + delta / 2; // round down |
| if (last > limit) { |
| last = limit; |
| } |
| while (dst < last) { |
| *dst++ = spec._fill; |
| } |
| break; |
| default: |
| // Everything else is equivalent to LEFT - distinction is for more specialized |
| // types such as integers. |
| dst = base + size; |
| last = dst + delta; |
| if (last > limit) { |
| last = limit; |
| } |
| while (dst < last) { |
| *dst++ = spec._fill; |
| } |
| break; |
| } |
| w.fill(min); |
| } else { |
| size_t max = spec._max; |
| if (max < extent) { |
| extent = max; |
| } |
| w.fill(extent); |
| } |
| } |
| |
| // 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; |
| } |
| |
| template <typename F> |
| void |
| Write_Aligned(BufferWriter &w, F const &f, BWFSpec::Align align, int width, char fill, char neg) |
| { |
| switch (align) { |
| case BWFSpec::Align::LEFT: |
| if (neg) { |
| w.write(neg); |
| } |
| f(); |
| while (width-- > 0) { |
| w.write(fill); |
| } |
| break; |
| case BWFSpec::Align::RIGHT: |
| while (width-- > 0) { |
| w.write(fill); |
| } |
| if (neg) { |
| w.write(neg); |
| } |
| f(); |
| break; |
| case BWFSpec::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 BWFSpec::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, BWFSpec 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 (neg_p) { |
| neg = '-'; |
| } else if (spec._sign != '-') { |
| neg = spec._sign; |
| } |
| |
| switch (spec._type) { |
| case 'x': |
| prefix2 = 'x'; |
| n = bw_fmt::To_Radix<16>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS); |
| break; |
| case 'X': |
| prefix2 = 'X'; |
| n = bw_fmt::To_Radix<16>(i, buff, sizeof(buff), bw_fmt::UPPER_DIGITS); |
| break; |
| case 'b': |
| prefix2 = 'b'; |
| n = bw_fmt::To_Radix<2>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS); |
| break; |
| case 'B': |
| prefix2 = 'B'; |
| n = bw_fmt::To_Radix<2>(i, buff, sizeof(buff), bw_fmt::UPPER_DIGITS); |
| break; |
| case 'o': |
| n = bw_fmt::To_Radix<8>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS); |
| break; |
| default: |
| prefix1 = 0; |
| n = bw_fmt::To_Radix<10>(i, buff, sizeof(buff), bw_fmt::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 == BWFSpec::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_Floating(BufferWriter &w, BWFSpec const &spec, double f, bool neg_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; |
| } |
| |
| uint64_t whole_part = static_cast<uint64_t>(f); |
| if (whole_part == f || spec._prec == 0) { // integral |
| return Format_Integer(w, spec, whole_part, neg_p); |
| } |
| |
| static constexpr char dec = '.'; |
| double frac; |
| 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 == BWFSpec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2 |
| |
| frac = f - whole_part; // split the number |
| |
| if (neg_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; |
| 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; |
| } |
| } |
| |
| uint64_t frac_part = static_cast<uint64_t>(frac * shift + 0.5 /* rounding */); |
| |
| l = bw_fmt::To_Radix<10>(whole_part, whole, sizeof(whole), bw_fmt::LOWER_DIGITS); |
| r = bw_fmt::To_Radix<10>(frac_part, fraction, sizeof(fraction), bw_fmt::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; |
| } |
| |
| /// Write out the @a data as hexadecimal, using @a digits as the conversion. |
| void |
| Hex_Dump(BufferWriter &w, std::string_view data, const char *digits) |
| { |
| const char *ptr = data.data(); |
| for (auto n = data.size(); n > 0; --n) { |
| char c = *ptr++; |
| w.write(digits[(c >> 4) & 0xF]); |
| w.write(digits[c & 0xf]); |
| } |
| } |
| |
| } // namespace bw_fmt |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, BWFSpec const &spec, std::string_view sv) |
| { |
| int width = static_cast<int>(spec._min); // amount left to fill. |
| if (spec._prec > 0) { |
| sv.remove_prefix(spec._prec); |
| } |
| |
| if ('x' == spec._type || 'X' == spec._type) { |
| const char *digits = 'x' == spec._type ? bw_fmt::LOWER_DIGITS : bw_fmt::UPPER_DIGITS; |
| width -= sv.size() * 2; |
| if (spec._radix_lead_p) { |
| w.write('0'); |
| w.write(spec._type); |
| width -= 2; |
| } |
| bw_fmt::Write_Aligned(w, [&w, &sv, digits]() { bw_fmt::Hex_Dump(w, sv, digits); }, spec._align, width, spec._fill, 0); |
| } else { |
| width -= sv.size(); |
| bw_fmt::Write_Aligned(w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0); |
| } |
| return w; |
| } |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span) |
| { |
| static const BWFormat default_fmt{"{:#x}@{:p}"}; |
| if (spec._ext.size() && 'd' == spec._ext.front()) { |
| const char *digits = 'X' == spec._type ? bw_fmt::UPPER_DIGITS : bw_fmt::LOWER_DIGITS; |
| if (spec._radix_lead_p) { |
| w.write('0'); |
| w.write(digits[33]); |
| } |
| bw_fmt::Hex_Dump(w, span.view(), digits); |
| } else { |
| w.print(default_fmt, span.size(), span.data()); |
| } |
| return w; |
| } |
| |
| /// Preparse format string for later use. |
| BWFormat::BWFormat(ts::TextView fmt) |
| { |
| BWFSpec lit_spec{BWFSpec::DEFAULT}; |
| int arg_idx = 0; |
| |
| while (fmt) { |
| std::string_view lit_str; |
| std::string_view spec_str; |
| bool spec_p = this->parse(fmt, lit_str, spec_str); |
| |
| if (lit_str.size()) { |
| lit_spec._ext = lit_str; |
| _items.emplace_back(lit_spec, &Format_Literal); |
| } |
| if (spec_p) { |
| bw_fmt::GlobalSignature gf = nullptr; |
| BWFSpec parsed_spec{spec_str}; |
| if (parsed_spec._name.size() == 0) { // no name provided, use implicit index. |
| parsed_spec._idx = arg_idx; |
| } |
| if (parsed_spec._idx < 0) { // name wasn't missing or a valid index, assume global name. |
| gf = bw_fmt::Global_Table_Find(parsed_spec._name); |
| } else { |
| ++arg_idx; // bump this if not a global name. |
| } |
| _items.emplace_back(parsed_spec, gf); |
| } |
| } |
| } |
| |
| BWFormat::~BWFormat() {} |
| |
| /// 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. |
| bool |
| BWFormat::parse(ts::TextView &fmt, std::string_view &literal, std::string_view &specifier) |
| { |
| TextView::size_type off; |
| |
| // 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 out only 1 brace. |
| literal = fmt.take_prefix_at(off + 1); |
| return false; |
| } else if ('}' == c1) { |
| throw std::invalid_argument("BWFormat:: Unopened } in format string."); |
| } else { |
| literal = std::string_view{fmt.data(), off}; |
| fmt.remove_prefix(off + 1); |
| } |
| } else { |
| throw std::invalid_argument("BWFormat: 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_at(off); |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| BWFormat::Format_Literal(BufferWriter &w, BWFSpec const &spec) |
| { |
| w.write(spec._ext); |
| } |
| |
| bw_fmt::GlobalSignature |
| bw_fmt::Global_Table_Find(std::string_view name) |
| { |
| if (name.size()) { |
| auto spot = bw_fmt::BWF_GLOBAL_TABLE.find(name); |
| if (spot != bw_fmt::BWF_GLOBAL_TABLE.end()) { |
| return spot->second; |
| } |
| } |
| return nullptr; |
| } |
| |
| std::ostream & |
| FixedBufferWriter::operator>>(std::ostream &s) const |
| { |
| return s << this->view(); |
| } |
| |
| ssize_t |
| FixedBufferWriter::operator>>(int fd) const |
| { |
| return ::write(fd, this->data(), this->size()); |
| } |
| |
| bool |
| bwf_register_global(std::string_view name, BWGlobalNameSignature formatter) |
| { |
| return ts::bw_fmt::BWF_GLOBAL_TABLE.emplace(name, formatter).second; |
| } |
| |
| BufferWriter & |
| bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Errno const &e) |
| { |
| // 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> 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: ", |
| }}; |
| // This provides convenient safe access to the errno short name array. |
| auto short_name = [](int n) { return n < static_cast<int>(SHORT_NAME.size()) ? SHORT_NAME[n] : "Unknown: "sv; }; |
| static const BWFormat 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 { |
| w.write(short_name(e._e)); |
| w.write(strerror(e._e)); |
| 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, BWFSpec 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. |
| ink_assert(date._fmt.data()[date._fmt.size() - 1] == 0 || date._fmt.data()[date._fmt.size()] == 0); |
| // 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.auxBuffer(), r, date._fmt.data(), &t); |
| } |
| if (n > 0) { |
| w.fill(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, BWFSpec const &spec, bwf::OptionalAffix const &opts) |
| { |
| return w.write(opts._prefix).write(opts._text).write(opts._suffix); |
| } |
| |
| } // namespace ts |
| |
| namespace |
| { |
| void |
| BWF_Timestamp(ts::BufferWriter &w, ts::BWFSpec const &spec) |
| { |
| // 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[32]; |
| std::time_t t = std::time(nullptr); |
| auto n = strftime(buff, sizeof(buff), "%Y %b %d %H:%M:%S", std::localtime(&t)); |
| w.write(buff, n); |
| } |
| |
| void |
| BWF_Now(ts::BufferWriter &w, ts::BWFSpec const &spec) |
| { |
| bwformat(w, spec, std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); |
| } |
| |
| void |
| BWF_Tick(ts::BufferWriter &w, ts::BWFSpec const &spec) |
| { |
| bwformat(w, spec, std::chrono::high_resolution_clock::now().time_since_epoch().count()); |
| } |
| |
| void |
| BWF_ThreadID(ts::BufferWriter &w, ts::BWFSpec const &spec) |
| { |
| bwformat(w, spec, pthread_self()); |
| } |
| |
| void |
| BWF_ThreadName(ts::BufferWriter &w, ts::BWFSpec const &spec) |
| { |
| char name[32]; // manual says at least 16, bump that up a bit. |
| ink_get_thread_name(name, sizeof(name)); |
| bwformat(w, spec, std::string_view{name}); |
| } |
| |
| static bool BW_INITIALIZED __attribute__((unused)) = []() -> bool { |
| ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("now", &BWF_Now); |
| ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("tick", &BWF_Tick); |
| ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("timestamp", &BWF_Timestamp); |
| ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("thread-id", &BWF_ThreadID); |
| ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("thread-name", &BWF_ThreadName); |
| return true; |
| }(); |
| |
| } // namespace |
| |
| namespace std |
| { |
| ostream & |
| operator<<(ostream &s, ts::FixedBufferWriter &w) |
| { |
| return s << w.view(); |
| } |
| } // namespace std |