| /** | |
| @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. | |
| Copyright 2019, Oath Inc. | |
| */ | |
| #include <string> | |
| #include <chrono> | |
| #include <swoc/bwf_std.h> | |
| #include <swoc/bwf_ip.h> | |
| #include "txn_box/common.h" | |
| #include "txn_box/Config.h" | |
| #include "txn_box/Context.h" | |
| using swoc::TextView; | |
| using namespace swoc::literals; | |
| using swoc::BufferWriter; | |
| using swoc::Errata; | |
| using swoc::Rv; | |
| // ---- | |
| namespace | |
| { | |
| struct join_visitor { | |
| swoc::BufferWriter &_w; | |
| TextView _glue; | |
| unsigned _recurse = 0; | |
| swoc::BufferWriter & | |
| glue() const | |
| { | |
| if (_w.size()) { | |
| _w.write(_glue); | |
| } | |
| return _w; | |
| } | |
| void | |
| operator()(feature_type_for<NIL>) const | |
| { | |
| } | |
| void | |
| operator()(feature_type_for<STRING> const &s) const | |
| { | |
| this->glue().write(s); | |
| } | |
| void | |
| operator()(feature_type_for<INTEGER> n) const | |
| { | |
| this->glue().print("{}", n); | |
| } | |
| void | |
| operator()(feature_type_for<BOOLEAN> flag) const | |
| { | |
| this->glue().print("{}", flag); | |
| } | |
| void | |
| operator()(feature_type_for<DURATION> d) const | |
| { | |
| this->glue().print("{}", d); | |
| } | |
| void | |
| operator()(feature_type_for<TUPLE> t) const | |
| { | |
| this->glue(); | |
| if (_recurse) { | |
| _w.write("[ "_tv); | |
| } | |
| auto lw = swoc::FixedBufferWriter{_w.aux_span()}; | |
| for (auto const &item : t) { | |
| std::visit(join_visitor{lw, _glue, _recurse + 1}, item); | |
| } | |
| _w.commit(lw.size()); | |
| if (_recurse) { | |
| _w.write(" ]"_tv); | |
| } | |
| } | |
| template <typename T> | |
| auto | |
| operator()(T const &) const -> EnableForFeatureTypes<T, void> | |
| { | |
| } | |
| }; | |
| } // namespace | |
| Feature | |
| Feature::join(Context &ctx, const swoc::TextView &glue) const | |
| { | |
| return {ctx.render_transient([=](BufferWriter &w) { std::visit(join_visitor{w, glue}, *this); })}; | |
| } | |
| // ---- | |
| /** Parse a string that consists of counts and units. | |
| * | |
| * Given a set of units, each of which is a list of names and a multiplier, parse a string. The | |
| * string contents must consist of (optional whitespace) with alternating counts and units, | |
| * starting with a count. Each count is multiplied by the value of the subsequent unit. Optionally | |
| * the parser can be set to allow counts without units, which are not multiplied. | |
| * | |
| * For example, if the units were [ "X", 10 ] , [ "L", 50 ] , [ "C", 100 ] , [ "M", 1000 ] | |
| * then the following strings would be parsed as | |
| * | |
| * - "1X" : 10 | |
| * - "1L3X" : 80 | |
| * - "2C" : 200 | |
| * - "1M 4C 4X" : 1,440 | |
| * - "3M 5 C3 X" : 3,530 | |
| */ | |
| template <typename E> class UnitParser | |
| { | |
| using self_type = UnitParser; ///< Self reference type. | |
| public: | |
| using value_type = E; ///< Integral type returned. | |
| using unit_type = swoc::Lexicon<value_type>; ///< Unit definition type. | |
| static constexpr value_type NIL{0}; | |
| /** Constructor. | |
| * | |
| * @param units A @c Lexicon of unit definitions. | |
| */ | |
| explicit UnitParser(unit_type &&units) noexcept; | |
| /** Constructor. | |
| * | |
| * @param units A @c Lexicon of unit definitions. | |
| * @param unit_required_p Specify if every number must have an attached unit. | |
| */ | |
| UnitParser(unit_type &&units, bool unit_required_p) noexcept; | |
| /** Set whether a unit is required. | |
| * | |
| * @param flag @c true if a unit is required, @c false if not. | |
| * @return @a this. | |
| */ | |
| self_type &unit_required(bool flag); | |
| /** Parse a string. | |
| * | |
| * @param src Input string. | |
| * @return The computed value if the input it valid, or an error report. | |
| */ | |
| Rv<value_type> operator()(swoc::TextView const &src) const noexcept; | |
| unit_type const & | |
| units() | |
| { | |
| return _units; | |
| } | |
| protected: | |
| bool _unit_required_p = true; ///< Whether unitless values are allowed. | |
| const unit_type _units; ///< Unit definitions. | |
| }; | |
| template <typename E> UnitParser<E>::UnitParser(UnitParser::unit_type &&units) noexcept : UnitParser(std::move(units), true) {} | |
| template <typename E> | |
| UnitParser<E>::UnitParser(UnitParser::unit_type &&units, bool unit_required_p) noexcept | |
| : _unit_required_p(unit_required_p), _units(std::move(units.set_default(NIL))) | |
| { | |
| } | |
| template <typename E> | |
| auto | |
| UnitParser<E>::unit_required(bool flag) -> self_type & | |
| { | |
| _unit_required_p = flag; | |
| return *this; | |
| } | |
| template <typename E> | |
| auto | |
| UnitParser<E>::operator()(swoc::TextView const &src) const noexcept -> Rv<value_type> | |
| { | |
| value_type zret{0}; | |
| TextView text = src; // Keep @a src around to report error offsets. | |
| while (text.ltrim_if(&isspace)) { | |
| // Get a count first. | |
| auto ptr = text.data(); // save for error reporting. | |
| auto count = text.clip_prefix_of(&isdigit); | |
| if (count.empty()) { | |
| return {NIL, Errata(S_ERROR, "Required count not found at offset {}", ptr - src.data())}; | |
| } | |
| // Should always parse correctly as @a count is a non-empty sequence of digits. | |
| auto n = svtou(count); | |
| // Next, the unit. | |
| ptr = text.ltrim_if(&isspace).data(); // save for error reporting. | |
| // Everything up to the next digit or whitespace. | |
| auto unit = text.clip_prefix_of([](char c) { return !(isspace(c) || isdigit(c)); }); | |
| if (unit.empty()) { | |
| if (_unit_required_p) { | |
| return {NIL, Errata(S_ERROR, "Required unit not found at offset {}", ptr - src.data())}; | |
| } | |
| zret += value_type{n}; // no metric -> unit metric. | |
| } else { | |
| auto mult = _units[unit]; // What's the multiplier? | |
| if (mult == NIL) { | |
| return {NIL, Errata(S_ERROR, "Unknown unit \"{}\" at offset {}", unit, ptr - src.data())}; | |
| } | |
| zret += mult * n; | |
| } | |
| } | |
| return zret; | |
| } | |
| // ---- | |
| namespace | |
| { | |
| struct bool_visitor { | |
| template <typename F> | |
| auto | |
| operator()(F const &) const -> EnableForFeatureTypes<F, bool> | |
| { | |
| TS_DBG("should not be here!"); | |
| return false; | |
| } | |
| bool | |
| operator()(feature_type_for<BOOLEAN> const &flag) const | |
| { | |
| return flag; | |
| } | |
| bool | |
| operator()(feature_type_for<NIL>) const | |
| { | |
| return false; | |
| } | |
| bool | |
| operator()(feature_type_for<STRING> const &s) const | |
| { | |
| return BoolNames[s] == True; | |
| } | |
| bool | |
| operator()(feature_type_for<INTEGER> const &n) const | |
| { | |
| return n != 0; | |
| } | |
| bool | |
| operator()(feature_type_for<FLOAT> const &f) const | |
| { | |
| return f != 0; | |
| } | |
| bool | |
| operator()(feature_type_for<IP_ADDR> const &addr) const | |
| { | |
| return addr.is_valid(); | |
| } | |
| bool | |
| operator()(feature_type_for<TUPLE> const &t) const | |
| { | |
| return t.size() > 0; | |
| } | |
| bool | |
| operator()(feature_type_for<DURATION> const &d) const | |
| { | |
| return d.count() != 0; | |
| } | |
| }; | |
| } // namespace | |
| auto | |
| Feature::as_bool() const -> type_for<BOOLEAN> | |
| { | |
| static bool_visitor visitor; | |
| return std::visit(visitor, *this); | |
| } | |
| // ---- | |
| namespace | |
| { | |
| struct integer_visitor { | |
| /// Target feature type. | |
| using ftype = feature_type_for<INTEGER>; | |
| /// Return type. | |
| using ret_type = Rv<ftype>; | |
| ftype _invalid; | |
| explicit integer_visitor(ftype invalid) : _invalid(invalid) {} | |
| ret_type | |
| operator()(feature_type_for<STRING> const &s) | |
| { | |
| TextView parsed; | |
| ftype zret = swoc::svtoi(s, &parsed); | |
| if (parsed.size() != s.size()) { | |
| return {_invalid, Errata(S_ERROR, "Invalid format for integer at offset {}", parsed.size() + 1)}; | |
| } | |
| return zret; | |
| } | |
| ret_type | |
| operator()(feature_type_for<INTEGER> n) | |
| { | |
| return n; | |
| } | |
| ret_type | |
| operator()(feature_type_for<FLOAT> f) | |
| { | |
| return ftype(f); | |
| } | |
| ret_type | |
| operator()(feature_type_for<BOOLEAN> flag) | |
| { | |
| return ftype(flag); | |
| } | |
| ret_type | |
| operator()(feature_type_for<TUPLE> t) | |
| { | |
| return t.size(); | |
| } | |
| template <typename F> | |
| auto | |
| operator()(F const &) -> EnableForFeatureTypes<F, ret_type> | |
| { | |
| return {_invalid, Errata(S_ERROR, "Feature of type {} cannot be coerced to type {}.", value_type_of<F>, INTEGER)}; | |
| } | |
| }; | |
| } // namespace | |
| auto | |
| Feature::as_integer(type_for<INTEGER> invalid) const -> Rv<type_for<INTEGER>> | |
| { | |
| return std::visit(integer_visitor{invalid}, *this); | |
| } | |
| // ---- | |
| // Duration conversion support. | |
| using namespace std::chrono; | |
| namespace swoc | |
| { | |
| template <> | |
| uintmax_t | |
| Lexicon_Hash(system_clock::duration d) | |
| { | |
| return static_cast<uintmax_t>(d.count()); | |
| } | |
| } // namespace swoc | |
| UnitParser<system_clock::duration> DurationParser{ | |
| UnitParser<system_clock::duration>::unit_type{UnitParser<system_clock::duration>::unit_type::with_multi{ | |
| {duration_cast<std::chrono::system_clock::duration>(nanoseconds(1)), {"ns", "nanoseconds"}}, | |
| {duration_cast<std::chrono::system_clock::duration>(microseconds(1)), {"us", "microseconds"}}, | |
| {duration_cast<std::chrono::system_clock::duration>(milliseconds(1)), {"ms", "milliseconds"}}, | |
| {duration_cast<std::chrono::system_clock::duration>(seconds(1)), {"s", "sec", "second", "seconds"}}, | |
| {duration_cast<std::chrono::system_clock::duration>(minutes(1)), {"m", "min", "minute", "minutes"}}, | |
| {duration_cast<std::chrono::system_clock::duration>(hours(1)), {"h", "hour", "hours"}}, | |
| {duration_cast<std::chrono::system_clock::duration>(days(1)), {"d", "day", "days"}}, | |
| {duration_cast<std::chrono::system_clock::duration>(days(7)), {"w", "week", "weeks"}}}}}; | |
| // Generate a list, ordered largest to smallest, of the duration name units. | |
| // The lambda constructs such a vector, which is then used to move construct @c DurationOrder. | |
| std::vector<decltype(DurationParser)::unit_type::iterator> DurationOrder{[]() { | |
| using I = decltype(DurationParser)::unit_type::iterator; | |
| auto const &lexicon = DurationParser.units(); | |
| auto n = lexicon.count(); | |
| std::vector<I> zret; | |
| zret.reserve(n); | |
| // Load the iterators into the vector. | |
| for (auto spot = lexicon.begin(); spot != lexicon.end(); ++spot) { | |
| zret.push_back(spot); | |
| } | |
| // Sort the iterators by scale, largest first. | |
| std::sort(zret.begin(), zret.end(), [](I const &lhs, I const &rhs) { return std::get<0>(*lhs) > std::get<0>(*rhs); }); | |
| return zret; | |
| }()}; | |
| namespace | |
| { | |
| /// Handlers for coercing feature types into duration. | |
| struct duration_visitor { | |
| /// Target feature type. | |
| using ftype = feature_type_for<DURATION>; | |
| /// Return type. | |
| using ret_type = Rv<ftype>; | |
| ftype _invalid; ///< Value to return if an error occurs. | |
| explicit duration_visitor(ftype invalid) : _invalid(invalid) {} | |
| ret_type operator()(feature_type_for<DURATION> const &d); | |
| ret_type operator()(feature_type_for<STRING> const &s); | |
| ret_type operator()(feature_type_for<TUPLE> t); | |
| template <typename F> | |
| auto | |
| operator()(F const &) -> EnableForFeatureTypes<F, ret_type> | |
| { | |
| return {_invalid, Errata(S_ERROR, "Feature of type {} cannot be coerced to type {}.", value_type_of<F>, INTEGER)}; | |
| } | |
| }; | |
| // It's already the correct type, pass it back. | |
| auto | |
| duration_visitor::operator()(feature_type_for<DURATION> const &d) -> ret_type | |
| { | |
| return d; | |
| } | |
| duration_visitor::ret_type | |
| duration_visitor::operator()(feature_type_for<STRING> const &s) | |
| { | |
| auto &&[n, errata] = DurationParser(s); | |
| if (!errata.is_ok()) { | |
| errata.note("Duration string was not a valid format."); | |
| return {_invalid, std::move(errata)}; | |
| } | |
| return {feature_type_for<DURATION>(n)}; | |
| } | |
| // Sum the durations in the tuple, coercing as it goes. | |
| duration_visitor::ret_type | |
| duration_visitor::operator()(feature_type_for<TUPLE> t) | |
| { | |
| ftype zret{0}; | |
| unsigned idx = 0; // Just for error reporting. | |
| for (auto const &item : t) { | |
| auto &&[value, errata]{std::visit(duration_visitor{_invalid}, item)}; | |
| if (!errata.is_ok()) { | |
| errata.note("The tuple element at index {} was not a valid duration.", idx); | |
| return {_invalid, std::move(errata)}; | |
| } | |
| ++idx; | |
| zret += value; | |
| } | |
| return zret; | |
| } | |
| } // namespace | |
| auto | |
| Feature::as_duration(type_for<DURATION> invalid) const -> Rv<type_for<DURATION>> | |
| { | |
| return std::visit(duration_visitor{invalid}, *this); | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| bool | |
| operator==(Feature const &lhs, Feature const &rhs) | |
| { | |
| auto lidx = lhs.index(); | |
| if (lidx != rhs.index()) { | |
| return false; | |
| } | |
| switch (lidx) { | |
| case IndexFor(NO_VALUE): | |
| case IndexFor(NIL): | |
| return true; | |
| case IndexFor(BOOLEAN): | |
| return std::get<IndexFor(BOOLEAN)>(lhs) == std::get<IndexFor(BOOLEAN)>(rhs); | |
| case IndexFor(INTEGER): | |
| return std::get<IndexFor(INTEGER)>(lhs) == std::get<IndexFor(INTEGER)>(rhs); | |
| case IndexFor(IP_ADDR): | |
| return std::get<IndexFor(IP_ADDR)>(lhs) == std::get<IndexFor(IP_ADDR)>(rhs); | |
| case IndexFor(DURATION): | |
| return std::get<IndexFor(DURATION)>(lhs) == std::get<IndexFor(DURATION)>(rhs); | |
| default: | |
| break; | |
| } | |
| return false; | |
| } | |
| bool | |
| operator<(Feature const &lhs, Feature const &rhs) | |
| { | |
| auto lidx = lhs.index(); | |
| if (lidx != rhs.index()) { | |
| return false; | |
| } | |
| switch (lidx) { | |
| case IndexFor(BOOLEAN): | |
| return std::get<IndexFor(BOOLEAN)>(lhs) < std::get<IndexFor(BOOLEAN)>(rhs); | |
| case IndexFor(INTEGER): | |
| return std::get<IndexFor(INTEGER)>(lhs) < std::get<IndexFor(INTEGER)>(rhs); | |
| case IndexFor(IP_ADDR): | |
| return std::get<IndexFor(IP_ADDR)>(lhs) < std::get<IndexFor(IP_ADDR)>(rhs); | |
| case IndexFor(DURATION): | |
| return std::get<IndexFor(DURATION)>(lhs) < std::get<IndexFor(DURATION)>(rhs); | |
| default: | |
| break; | |
| } | |
| return false; | |
| } | |
| bool | |
| operator<=(Feature const &lhs, Feature const &rhs) | |
| { | |
| auto lidx = lhs.index(); | |
| if (lidx != rhs.index()) { | |
| return false; | |
| } | |
| switch (lidx) { | |
| case IndexFor(NIL): | |
| return true; | |
| case IndexFor(BOOLEAN): | |
| return std::get<IndexFor(BOOLEAN)>(lhs) <= std::get<IndexFor(BOOLEAN)>(rhs); | |
| case IndexFor(INTEGER): | |
| return std::get<IndexFor(INTEGER)>(lhs) <= std::get<IndexFor(INTEGER)>(rhs); | |
| case IndexFor(IP_ADDR): | |
| return std::get<IndexFor(IP_ADDR)>(lhs) <= std::get<IndexFor(IP_ADDR)>(rhs); | |
| case IndexFor(DURATION): | |
| return std::get<IndexFor(DURATION)>(lhs) <= std::get<IndexFor(DURATION)>(rhs); | |
| default: | |
| break; | |
| } | |
| return false; | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| namespace swoc | |
| { | |
| BufferWriter & | |
| bwformat(BufferWriter &w, bwf::Spec const &, feature_type_for<NO_VALUE>) | |
| { | |
| return w.write("!NO_VALUE"); | |
| } | |
| BufferWriter & | |
| bwformat(BufferWriter &w, bwf::Spec const &, feature_type_for<NIL>) | |
| { | |
| return w.write("NULL"); | |
| } | |
| BufferWriter & | |
| bwformat(BufferWriter &w, bwf::Spec const &spec, ValueType type) | |
| { | |
| if (spec.has_numeric_type()) { | |
| return bwformat(w, spec, static_cast<unsigned>(type)); | |
| } | |
| return bwformat(w, spec, ValueTypeNames[type]); | |
| } | |
| BufferWriter & | |
| bwformat(BufferWriter &w, bwf::Spec const &spec, ValueMask const &mask) | |
| { | |
| auto span{w.aux_span()}; | |
| if (span.size() > spec._max) { | |
| span = span.prefix(spec._max); | |
| } | |
| swoc::FixedBufferWriter lw{span}; | |
| if (mask.any()) { | |
| for (auto const &[e, v] : ValueTypeNames) { | |
| if (!mask[e]) { | |
| continue; | |
| } | |
| if (lw.extent()) { | |
| lw.write(", "); | |
| } | |
| bwformat(lw, spec, v); | |
| } | |
| } else { | |
| bwformat(lw, spec, "*no value"_tv); | |
| } | |
| w.commit(lw.extent()); | |
| return w; | |
| } | |
| BufferWriter & | |
| bwformat(BufferWriter &w, bwf::Spec const &spec, FeatureTuple const &t) | |
| { | |
| if (t.count() > 0) { | |
| bwformat(w, spec, t[0]); | |
| for (auto &&f : t.subspan(1, t.count() - 1)) { | |
| w.write(", "); | |
| bwformat(w, spec, f); | |
| } | |
| } | |
| return w; | |
| } | |
| BufferWriter & | |
| bwformat(BufferWriter &w, bwf::Spec const &spec, Feature const &feature) | |
| { | |
| if (is_nil(feature)) { | |
| return bwformat(w, spec, "NULL"_tv); | |
| } else { | |
| auto visitor = [&](auto &&arg) -> BufferWriter & { return bwformat(w, spec, arg); }; | |
| return std::visit(visitor, feature); | |
| } | |
| } | |
| BufferWriter & | |
| bwformat(BufferWriter &w, bwf::Spec const &spec, feature_type_for<DURATION> const &d) | |
| { | |
| bool sep_p = false; | |
| auto n = d.count(); | |
| for (auto item : DurationOrder) { | |
| auto scale = std::get<0>(*item).count(); | |
| auto c = n / scale; | |
| if (c > 0) { | |
| if (sep_p) { | |
| w.write(' '); | |
| } | |
| sep_p = true; | |
| bwformat(w, spec, c); | |
| w.write(' '); | |
| w.write(std::get<1>(*item)); | |
| n -= c * scale; | |
| } | |
| } | |
| return w; | |
| } | |
| } // namespace swoc | |
| BufferWriter & | |
| bwformat(BufferWriter &w, swoc::bwf::Spec const &spec, ActiveType const &type) | |
| { | |
| bwformat(w, spec, type._base_type); | |
| if (type._tuple_type.any()) { | |
| w.write(", Tuples of ["); | |
| bwformat(w, spec, type._tuple_type); | |
| w.write(']'); | |
| } | |
| return w; | |
| } |