| // SPDX-License-Identifier: Apache-2.0 |
| // Copyright Apache Software Foundation 2019 |
| /** @file |
| |
| Basic formatting support for @c BufferWriter. |
| */ |
| |
| #pragma once |
| |
| #include <cstdlib> |
| #include <utility> |
| #include <cstring> |
| #include <vector> |
| #include <unordered_map> |
| #include <string> |
| #include <iosfwd> |
| #include <string_view> |
| #include <functional> |
| #include <tuple> |
| #include <any> |
| #include <array> |
| |
| #include "swoc/swoc_version.h" |
| #include "swoc/TextView.h" |
| #include "swoc/MemSpan.h" |
| #include "swoc/MemArena.h" |
| #include "swoc/BufferWriter.h" |
| #include "swoc/swoc_meta.h" |
| |
| namespace swoc { inline namespace SWOC_VERSION_NS { |
| namespace bwf { |
| /** Parsed version of a format specifier. |
| * |
| * Literals are represented as an instance of this class, with the type set to |
| * @c LITERAL_TYPE and the literal text in the @a _ext field. |
| */ |
| struct Spec { |
| using self_type = Spec; ///< Self reference type. |
| |
| static constexpr char DEFAULT_TYPE = 'g'; ///< Default format type. |
| static constexpr char INVALID_TYPE = 0; ///< Type for missing or invalid specifier. |
| static constexpr char LITERAL_TYPE = '"'; ///< Internal type to mark a literal. |
| static constexpr char CAPTURE_TYPE = 1; ///< Internal type to mark a capture. |
| |
| static constexpr char SIGN_ALWAYS = '+'; ///< Always print a sign character. |
| static constexpr char SIGN_NEVER = ' '; ///< Never print a sign character. |
| static constexpr char SIGN_NEG = '-'; ///< Print a sign character only for negative values (default). |
| |
| /// Constructor a default instance. |
| constexpr Spec() {} |
| |
| /// Construct by parsing @a fmt. |
| Spec(const TextView& fmt); |
| |
| /// Parse a specifier. |
| /// State is not reset, @a this should be default constructed before calling. |
| bool parse(TextView fmt); |
| |
| char _fill = ' '; ///< Fill character. |
| char _sign = SIGN_NEG; ///< Numeric sign style. |
| /// Flag for how to align the output inside a limited width field. |
| enum class Align : char { |
| NONE, ///< No alignment. |
| LEFT, ///< Left alignment '<'. |
| RIGHT, ///< Right alignment '>'. |
| CENTER, ///< Center alignment '^'. |
| SIGN ///< Align plus/minus sign before numeric fill. '=' |
| } _align = Align::NONE; ///< Output field alignment. |
| char _type = DEFAULT_TYPE; ///< Type / radix indicator. |
| bool _radix_lead_p = false; ///< Print leading radix indication. |
| // @a _min is unsigned because there's no point in an invalid default, 0 works fine. |
| unsigned int _min = 0; ///< Minimum width. |
| int _prec = -1; ///< Precision |
| unsigned int _max = std::numeric_limits<unsigned int>::max(); ///< Maximum width |
| int _idx = -1; ///< Positional "name" of the specification. |
| std::string_view _name; ///< Name of the specification. |
| std::string_view _ext; ///< Extension if provided. |
| |
| /// Global default instance for use in situations where a format specifier isn't available. |
| static const self_type DEFAULT; |
| |
| /// Validate @a c is a specifier type indicator. |
| static bool is_type(char c); |
| |
| /// Check if the type flag is numeric. |
| static bool is_numeric_type(char c); |
| |
| /// Check if the type is an upper case variant. |
| static bool is_upper_case_type(char c); |
| |
| /// Check if the type @a in @a this is numeric. |
| bool has_numeric_type() const; |
| |
| /// Check if the type in @a this is an upper case variant. |
| bool has_upper_case_type() const; |
| |
| /// Check if the type is a raw pointer. |
| bool has_pointer_type() const; |
| |
| /// Check if the type is valid. |
| bool has_valid_type() const; |
| |
| protected: |
| /// Validate character is alignment character and return the appropriate enum value. |
| Align align_of(char c); |
| |
| /// Validate is sign indicator. |
| bool is_sign(char c); |
| |
| /// Handrolled initialization the character syntactic property data. |
| static const struct Property { |
| Property(); ///< Default constructor, creates initialized flag set. |
| /// Flag storage, indexed by character value. |
| uint8_t _data[0x100]; |
| /// Flag mask values. |
| static constexpr uint8_t ALIGN_MASK = 0x0F; ///< Alignment type. |
| static constexpr uint8_t TYPE_CHAR = 0x10; ///< A valid type character. |
| static constexpr uint8_t UPPER_TYPE_CHAR = 0x20; ///< Upper case flag. |
| static constexpr uint8_t NUMERIC_TYPE_CHAR = 0x40; ///< Numeric output. |
| static constexpr uint8_t SIGN_CHAR = 0x80; ///< Is sign character. |
| } _prop; |
| }; |
| |
| /** Format string support. |
| * |
| * This contains the parsing logic for format strings and also serves as the type for pre-compiled |
| * format string. |
| * |
| * When used by the print formatting logic, there is an abstraction layer, "extraction", which |
| * performs the equivalent of the @c parse method. This allows the formatting to treat |
| * pre-compiled or immediately parsed format strings the same. It also enables formatted print |
| * support any parser that can deliver literals and @c Spec instances. |
| */ |
| class Format { |
| public: |
| /// Construct from a format string @a fmt. |
| Format(TextView fmt); |
| |
| /// Extraction support for TextView. |
| struct TextViewExtractor { |
| TextView _fmt; ///< Format string. |
| |
| /// @return @c true if more format string, @c false if none. |
| explicit operator bool() const; |
| |
| /** Extract next formatting elements. |
| * |
| * @param literal_v [out] The next literal. |
| * @param spec [out] The next format specifier. |
| * @return @c true if @a spec was filled out, @c false if no specifier was found. |
| */ |
| bool operator()(std::string_view& literal_v, Spec& spec); |
| |
| /** Parse elements of a format string. |
| |
| @param fmt The format string [in|out] |
| @param literal A literal if found |
| @param specifier A specifier if found (less enclosing braces) |
| @return @c true if a specifier was found, @c false if not. |
| |
| Pull off the next literal and/or specifier from @a fmt. The return value distinguishes |
| the case of no specifier found (@c false) or an empty specifier (@c true). |
| |
| */ |
| static bool parse(TextView& fmt, std::string_view& literal, std::string_view& specifier); |
| }; |
| |
| /// Wrap the format string in an extractor. |
| static TextViewExtractor bind(TextView fmt); |
| |
| /// Extraction support for pre-parsed format strings. |
| struct FormatExtractor { |
| const std::vector<Spec>& _fmt; ///< Parsed format string. |
| int _idx = 0; ///< Element index. |
| explicit operator bool() const; |
| |
| bool operator()(std::string_view& literal_v, Spec& spec); |
| }; |
| |
| /// Wrap the format instance in an extractor. |
| FormatExtractor bind() const; |
| |
| protected: |
| /// Default constructor for use by subclasses with alternate formatting. |
| Format() = default; |
| |
| std::vector<Spec> _items; ///< Items from format string. |
| }; |
| |
| // Name binding - support for having format specifier names. |
| |
| /** Signature for a functor bound to a name. |
| * |
| * @param w The output. |
| * @param spec The format specifier. |
| * |
| * The functor is expected to write to @a w based on @a spec. |
| */ |
| using ExternalGeneratorSignature = BufferWriter&(BufferWriter& w, Spec const& spec); |
| |
| /** Base class for implementing a name binding functor. |
| * |
| * This expected to be inherited by other classes that provide the name binding service. |
| * It does a few small but handy things. |
| * |
| * - Force a virtual destructor. |
| * - Force the implementation of the binding method by declaring it as a pure virtual. |
| * - Provide a standard "missing name" method. |
| */ |
| class NameBinding { |
| public: |
| virtual ~NameBinding(); ///< Force virtual destructor. |
| |
| /** Generate output text for @a name on the output @a w using the format specifier @a spec. |
| * This must match the @c BoundNameSignature type. |
| * |
| * @param w Output stream. |
| * @param spec Parsed format specifier. |
| * |
| * @note The tag name can be found in @c spec._name. |
| * |
| * @return @a w |
| */ |
| virtual BufferWriter& operator()(BufferWriter& w, Spec const& spec) const = 0; |
| |
| protected: |
| /** Standardized missing name method. |
| * |
| * @param w The destination buffer. |
| * @param spec Format specifier, used to determine the invalid name. |
| * @return @a w |
| */ |
| static BufferWriter& err_invalid_name(BufferWriter& w, Spec const& spec); |
| }; |
| |
| /** An explicitly empty set of bound names. |
| * |
| * To simplify the overall implementation, a name binding is always required to format output. |
| * This class is used in situations where there is no available binding or such names would not be |
| * useful. This class with @c throw on any attempt to use a name. |
| */ |
| class NilBinding : public NameBinding { |
| public: |
| /// Do name based formatted output. |
| /// This always throws an exception. |
| BufferWriter& operator()(BufferWriter&, Spec const&) const override; |
| }; |
| |
| /** Associate generators with names. |
| * |
| * @tparam F The function signature for generators in this container. |
| * |
| * This is a base class used by different types of name containers. It is not expected to be used |
| * directly. A subclass should inherit from this by providing a function type @a F that is |
| * suitable for the subclass generators. |
| */ |
| template<typename F> class NameMap { |
| private: |
| using self_type = NameMap; ///< self reference type. |
| public: |
| /// Signature for generators. |
| using Generator = std::function<F>; |
| |
| /// Construct an empty container. |
| NameMap(); |
| |
| /// Construct and assign the names and generators in @a list |
| NameMap(std::initializer_list<std::tuple<std::string_view, Generator const&>> list); |
| |
| /** Assign the @a generator to the @a name. |
| * |
| * @param name Name associated with the @a generator. |
| * @param generator The generator function. |
| */ |
| self_type& assign(std::string_view const& name, Generator const& generator); |
| |
| protected: |
| /// Copy @a name in to local storage and return a view of it. |
| std::string_view localize(std::string_view const& name); |
| |
| using Map = std::unordered_map<std::string_view, Generator>; |
| Map _map; ///< Mapping of name -> generator |
| MemArena _arena{1024}; ///< Local name storage. |
| }; |
| |
| /** A class to hold external / context-free name bindings. |
| * |
| * These names access external data and therefore have no context. An externally accessible |
| * singleton instance of this is used as the default if no explicit name set is provided. This |
| * enables the executable to establish a set of global names to be used. |
| */ |
| class ExternalNames : public NameMap<ExternalGeneratorSignature>, public NameBinding { |
| using self_type = ExternalNames; ///< Self reference type. |
| using super_type = NameMap<ExternalGeneratorSignature>; ///< Super class. |
| using Map = super_type::Map; ///< Inherit from superclass. |
| |
| public: |
| using super_type::super_type; // import constructors. |
| |
| /// The bound accessor is this class. |
| NameBinding const& bind() const; |
| |
| /// Bound name access. |
| BufferWriter& operator()(BufferWriter& w, const Spec& spec) const override; |
| |
| /// @copydoc NameMap::assign(std::string_view const &name, Generator const &generator) |
| }; |
| |
| /** Associate names with context dependent generators. |
| * |
| * @tparam T The context type. This is used directly. If the context needs to be @c const |
| * then this parameter should make that explicit, e.g. @c ContextNames<const Context>. This |
| * parameter is accessible via the @c context_type alias. |
| * |
| * This provides a name binding that also has a local context, provided at the formatting call |
| * site. The functors have access to this context and are presumed to use it to generate output. |
| * This binding can also contain external generators which do not get access to the context to |
| * make it convenient to add external generators as well as context generators. |
| * |
| * A context functor should have the signature |
| * @code |
| * BufferWriter & generator(BufferWriter & w, const Spec & spec, T & context); |
| * @endcode |
| * |
| * @a context will be the context for the binding passed to the formatter. |
| * |
| * This is used by the formatting logic by calling the @c bind method with a context object. |
| */ |
| template<typename T> |
| class ContextNames : public NameMap<BufferWriter&(BufferWriter&, const Spec&, T&)> { |
| private: |
| using self_type = ContextNames; ///< self reference type. |
| using super_type = NameMap<BufferWriter&(BufferWriter&, const Spec&, T&)>; |
| using Map = typename super_type::Map; |
| |
| public: |
| using context_type = T; ///< Export for external convenience. |
| /// Functional type for a generator. |
| using Generator = typename super_type::Generator; |
| /// Signature for an external (context-free) generator. |
| using ExternalGenerator = std::function<ExternalGeneratorSignature>; |
| |
| using super_type::super_type; // inherit @c super_type constructors. |
| |
| class Binding : public NameBinding { |
| public: |
| /** Override of virtual method to provide an implementation. |
| * |
| * @param w Output. |
| * @param spec Format specifier for output. |
| * @return @a w |
| * |
| * This is called from the formatting logic to generate output for a named specifier. Subclasses |
| * that need to handle name dispatch differently need only override this method. |
| */ |
| BufferWriter& |
| operator()(BufferWriter& w, const Spec& spec) const override { |
| return _names(w, spec, _ctx); |
| } |
| |
| protected: |
| Binding(ContextNames const& names, context_type& ctx) : _ctx(ctx), _names(names) {} |
| |
| context_type& _ctx; ///< Context for generators. |
| ContextNames const& _names; ///< Base set of names. |
| |
| friend ContextNames; |
| }; |
| |
| /** Assign the external generator @a bg to @a name. |
| * |
| * This is used for generators in the namespace that do not use the context. |
| * |
| * @param name Name associated with the generator. |
| * @param bg An external generator that requires no context. |
| * @return @c *this |
| */ |
| self_type& assign(std::string_view const& name, const ExternalGenerator& bg); |
| |
| /** Assign the @a generator to the @a name. |
| * |
| * @param name Name associated with the @a generator. |
| * @param generator The generator function. |
| * |
| * @internal This is a covarying override of the super type, added to covary and |
| * provide documentation. |
| */ |
| self_type& assign(std::string_view const& name, Generator const& generator); |
| |
| /** Bind the name map to a specific @a context. |
| * |
| * @param context The instance of @a T to use in the generators. |
| * @return A reference to an internal instance of a subclass of the protocol class @c BoundNames. |
| * |
| * This is used when passing the context name map to the formatter. |
| */ |
| Binding bind(context_type& context); |
| |
| protected: |
| /** Generate output based on the name in @a spec. |
| * |
| * @param w Output. |
| * @param spec Format specifier for output. |
| * @param ctx The context object. |
| * @return @a w |
| * |
| * This is called from the formatting logic to generate output for a named specifier. Subclasses |
| * that need to handle name dispatch differently should override this method. This method |
| * performs a name lookup in the local nameset. |
| */ |
| virtual BufferWriter& operator()(BufferWriter& w, const Spec& spec, context_type& ctx) const; |
| }; |
| |
| /** Default global names. |
| * This nameset is used if no other is provided. Therefore bindings added to this nameset will be |
| * available in the default formatting use. |
| */ |
| extern ExternalNames Global_Names; |
| |
| // --------------- Implementation -------------------- |
| /// --- Spec --- |
| |
| inline Spec::Align |
| Spec::align_of(char c) { |
| return static_cast<Align>(_prop._data[static_cast<unsigned>(c)] & Property::ALIGN_MASK); |
| } |
| |
| inline bool |
| Spec::is_sign(char c) { |
| return _prop._data[static_cast<unsigned>(c)] & Property::SIGN_CHAR; |
| } |
| |
| inline bool |
| Spec::is_type(char c) { |
| return _prop._data[static_cast<unsigned>(c)] & Property::TYPE_CHAR; |
| } |
| |
| inline bool |
| Spec::is_upper_case_type(char c) { |
| return _prop._data[static_cast<unsigned>(c)] & Property::UPPER_TYPE_CHAR; |
| } |
| |
| inline bool |
| Spec::is_numeric_type(char c) { |
| return _prop._data[static_cast<unsigned>(c)] & Property::NUMERIC_TYPE_CHAR; |
| } |
| |
| inline bool |
| Spec::has_numeric_type() const { |
| return _prop._data[static_cast<unsigned>(_type)] & Property::NUMERIC_TYPE_CHAR; |
| } |
| |
| inline bool |
| Spec::has_upper_case_type() const { |
| return _prop._data[static_cast<unsigned>(_type)] & Property::UPPER_TYPE_CHAR; |
| } |
| |
| inline bool |
| Spec::has_pointer_type() const { |
| return _type == 'p' || _type == 'P'; |
| } |
| |
| inline bool |
| Spec::has_valid_type() const { |
| return _type != INVALID_TYPE; |
| } |
| |
| inline auto |
| Format::bind(swoc::TextView fmt) -> TextViewExtractor { |
| return {fmt}; |
| } |
| |
| inline auto |
| Format::bind() const -> FormatExtractor { |
| return {_items}; |
| } |
| |
| inline Format::TextViewExtractor::operator bool() const { return !_fmt.empty(); } |
| |
| inline Format::FormatExtractor::operator bool() const { |
| return _idx < static_cast<int>(_fmt.size()); |
| } |
| |
| /// --- Names / Generators --- |
| |
| inline BufferWriter& |
| NameBinding::err_invalid_name(BufferWriter& w, const Spec& spec) { |
| return w.print("{{~{}~}}", spec._name); |
| } |
| |
| inline BufferWriter& |
| NilBinding::operator()(BufferWriter&, bwf::Spec const&) const { |
| throw std::runtime_error("Use of nil bound names in BW formatting"); |
| } |
| |
| template<typename T> |
| inline auto |
| ContextNames<T>::bind(context_type& ctx) -> Binding { |
| return {*this, ctx}; |
| } |
| |
| template<typename T> |
| BufferWriter& |
| ContextNames<T>::operator()(BufferWriter& w, const Spec& spec, context_type& ctx) const { |
| if (!spec._name.empty()) { |
| if (auto spot = super_type::_map.find(spec._name); spot != super_type::_map.end()) { |
| spot->second(w, spec, ctx); |
| } else { |
| Binding::err_invalid_name(w, spec); |
| } |
| } |
| return w; |
| } |
| |
| template<typename F> NameMap<F>::NameMap() {} |
| |
| template<typename F> |
| NameMap<F>::NameMap(std::initializer_list<std::tuple<std::string_view, const Generator&>> list) { |
| for (auto &&[name, generator] : list) { |
| this->assign(name, generator); |
| } |
| } |
| |
| template<typename F> |
| std::string_view |
| NameMap<F>::localize(std::string_view const& name) { |
| auto span = _arena.alloc(name.size()); |
| memcpy(span, name); |
| return span.view(); |
| } |
| |
| template<typename F> |
| auto |
| NameMap<F>::assign(std::string_view const& name, Generator const& generator) -> self_type& { |
| _map[this->localize(name)] = generator; |
| return *this; |
| } |
| |
| inline BufferWriter& |
| ExternalNames::operator()(BufferWriter& w, const Spec& spec) const { |
| if (!spec._name.empty()) { |
| if (auto spot = _map.find(spec._name); spot != _map.end()) { |
| spot->second(w, spec); |
| } else { |
| this->err_invalid_name(w, spec); |
| } |
| } |
| return w; |
| } |
| |
| inline NameBinding const& |
| ExternalNames::bind() const { |
| return *this; |
| } |
| |
| template<typename T> |
| auto |
| ContextNames<T>::assign(std::string_view const& name, ExternalGenerator const& bg) -> self_type& { |
| // wrap @a bg in a shim that discards the context so it can be stored in the map. |
| super_type::assign(name, [bg](BufferWriter& w, Spec const& spec |
| , context_type&) -> BufferWriter& { return bg(w, spec); }); |
| return *this; |
| } |
| |
| template<typename T> |
| auto |
| ContextNames<T>::assign(std::string_view const& name, Generator const& g) -> self_type& { |
| super_type::assign(name, g); |
| return *this; |
| } |
| |
| /// --- Formatting --- |
| |
| /// Internal signature for template generated formatting. |
| /// @a args is a forwarded tuple of arguments to be processed. |
| template<typename TUPLE> using ArgFormatterSignature = BufferWriter& (*)(BufferWriter& w |
| , Spec const& |
| , TUPLE const& args); |
| |
| /// Internal error / reporting message generators |
| void Err_Bad_Arg_Index(BufferWriter& w, int i, size_t n); |
| |
| // MSVC will expand the parameter pack inside a lambda but not gcc, so this indirection is required. |
| |
| /// This selects the @a I th argument in the @a TUPLE arg pack and calls the formatter on it. This |
| /// (or the equivalent lambda) is needed because the array of formatters must have a homogenous |
| /// signature, not vary per argument. Effectively this indirection erases the type of the specific |
| /// argument being formatted. Instances of this have the signature @c ArgFormatterSignature. |
| template<typename TUPLE, size_t I> |
| BufferWriter& |
| Arg_Formatter(BufferWriter& w, Spec const& spec, TUPLE const& args) { |
| return bwformat(w, spec, std::get<I>(args)); |
| } |
| |
| /// This exists only to expand the index sequence into an array of formatters for the tuple type |
| /// @a TUPLE. Due to language limitations it cannot be done directly. The formatters can be |
| /// accessed via standard array access in contrast to templated tuple access. The actual array is |
| /// static and therefore at run time the only operation is loading the address of the array. |
| template<typename TUPLE, size_t... N> |
| ArgFormatterSignature<TUPLE> * |
| Get_Arg_Formatter_Array(std::index_sequence<N...>) { |
| static ArgFormatterSignature<TUPLE> fa[sizeof...(N)] = {&bwf::Arg_Formatter<TUPLE, N>...}; |
| return fa; |
| } |
| |
| /// Perform alignment adjustments / fill on @a w of the content in @a lw. |
| /// This is the normal mechanism, in cases where the length can be known or limited before |
| /// conversion, it can be more efficient to work in a temporary local buffer and copy out |
| /// as needed without moving data in the output buffer. |
| void Adjust_Alignment(BufferWriter& aux, Spec const& spec); |
| |
| /** Format @a n as an integral value. |
| * |
| * @param w Output buffer. |
| * @param spec Format specifier. |
| * @param n Input value to format. |
| * @param negative_p Input value should be treated as a negative value. |
| * @return @a w |
| * |
| * A leading sign character will be output based on @a spec and @a negative_p. |
| */ |
| BufferWriter& Format_Integer(BufferWriter& w, Spec const& spec, uintmax_t n, bool negative_p); |
| |
| /** Format @a f as a floating point value. |
| * |
| * @param w Output buffer. |
| * @param spec Format specifier. |
| * @param f Input value to format. |
| * @param negative_p Input value shoudl be treated as a negative value. |
| * @return @a w |
| * |
| * A leading sign character will be output based on @a spec and @a negative_p. |
| */ |
| BufferWriter& Format_Float(BufferWriter& w, Spec const& spec, double f, bool negative_p); |
| |
| /** Format output as a hexadecimal dump. |
| * |
| * @param w Output buffer. |
| * @param view Input view. |
| * @param digits Digit array for hexadecimal digits. |
| * |
| * This dumps the memory in the @a view as a hexadecimal string. |
| */ |
| void Format_As_Hex(BufferWriter& w, std::string_view view, const char *digits); |
| |
| /* Capture support, which allows format extractors to capture arguments and consume them. |
| * This was built in order to support C style formatting, which needs to capture arguments |
| * to set the minimum width and/or the precision of other arguments. |
| * |
| * The key component is the ability to dynamically access an element of a tuple using |
| * @c std::any. |
| * |
| * Note: Much of this was originally in the meta support but it caused problems in use if |
| * the tuple header wasn't also included. I was unable to determine why, so this code doesn't |
| * depend on tuple explicitly. |
| */ |
| /// The signature for accessing an element of a tuple. |
| template<typename T> using TupleAccessorSignature = std::any (*)(T const& t); |
| |
| /// Template access method. |
| template<size_t IDX, typename T> |
| std::any |
| TupleAccessor(T const& t) { |
| return std::any(&std::get<IDX>(t)); |
| } |
| |
| /// Create and return an array of specialized accessors, indexed by tuple index. |
| template<typename T, size_t... N> |
| std::array<TupleAccessorSignature<T>, sizeof...(N)>& |
| Tuple_Accessor_Array(std::index_sequence<N...>) { |
| static std::array<TupleAccessorSignature<T>, sizeof...(N)> accessors = {&TupleAccessor<N>...}; |
| return accessors; |
| } |
| |
| /// Get the Nth element of the tuple as @c std::any. |
| template<typename T> |
| std::any |
| Tuple_Nth(T const& t, size_t idx) { |
| return Tuple_Accessor_Array<T>(std::make_index_sequence<std::tuple_size<T>::value>())[idx](t); |
| } |
| |
| /// If capture is used, the format extractor must provide a @c capture method. This isn't required |
| /// so make it compile time optional, but throw if the extractor sets up for capture and didn't |
| /// provide one. |
| template<typename F> |
| auto |
| arg_capture(F&&, BufferWriter&, Spec const&, std::any&&, swoc::meta::CaseTag<0>) -> void { |
| throw std::runtime_error("Capture specification used in format extractor that does not support capture"); |
| } |
| |
| template<typename F> |
| auto |
| arg_capture(F&& f, BufferWriter& w, Spec const& spec, std::any&& value, swoc::meta::CaseTag<1>) |
| -> decltype(f.capture(w, spec, value)) { |
| return f.capture(w, spec, value); |
| } |
| |
| /** Extract the specifier type from an Extractor. |
| * |
| * @tparam EXTRACTOR Format extractor functor type. |
| * @tparam VIEW String view argument type. |
| * @tparam SPEC Specifier argument type. |
| * @return A value of type @a SPEC |
| * |
| * This is never called - it exists to extract @a SPEC from a format extractor functor to be used |
| * to declare the specifier instance passed to the format extractor. When used in this fashion |
| * with @c decltype the extracted type is a reference and that must be removed for the actual |
| * declaration type. The purpose is to enable format extractors to subclass @c bwf::Spec to |
| * pass additional information along, particularly to a name binding without interfering with |
| * the base use case. |
| */ |
| template<typename EXTRACTOR, typename VIEW, typename SPEC> |
| auto |
| extractor_spec_type(bool (EXTRACTOR::*)(VIEW, SPEC)) -> SPEC { |
| } |
| |
| /** A pack of arguments for formatting. |
| * |
| * @internal After much consideration, I decided this was the correct choice, to enable type |
| * erasure of the arguments to the base formatting logic. This costs a virtual function dispatch |
| * but prevents the formatting logic from being duplicated for every permutation of arguments. |
| * Overall, for any reasonably sized project, I think this is the better option. |
| * |
| * This also supports passing arguments in other than a tuple, which is necessary in order to |
| * pass arguments in various containers such as a vector. |
| */ |
| class ArgPack { |
| public: |
| virtual ~ArgPack() = default; /// Force virtual destructor for subclasses. |
| |
| /** Get argument at index @a idx. |
| * |
| * @param idx Argument index. |
| * @return The argument value. |
| * |
| * In general the arguments will be stored by reference and so the returned @c std::any |
| * instance will be a reference type. |
| */ |
| virtual std::any capture(unsigned idx) const = 0; |
| |
| /** Generate formatted output for an argument. |
| * |
| * @param w Output. |
| * @param spec Formatting specifier. |
| * @param idx Argument index. |
| * |
| * @return @a w |
| */ |
| virtual BufferWriter& print(BufferWriter& w, Spec const& spec, unsigned idx) const = 0; |
| |
| /// Number of arguments in the pack. |
| virtual unsigned count() const = 0; |
| }; |
| |
| /** An argument pack based on a reference tuple. |
| * |
| * @tparam Args Type of arguments in the tuple. |
| * |
| * This contains a reference to the tuple, and so is only suitable for passing as a temporary. |
| * |
| */ |
| template<typename... Args> class ArgTuple : public ArgPack { |
| public: |
| /// Construct from a tuple. |
| ArgTuple(std::tuple<Args...> const& tuple) : _tuple(tuple) {} |
| |
| protected: |
| /// Numnber of arguments in the tuple. |
| unsigned count() const override; |
| |
| /// Generate formatted output on @a w for argument at @a idx. |
| BufferWriter& print(BufferWriter& w, Spec const& spec, unsigned idx) const override; |
| |
| /// Capture the @a idx argument for later use. |
| std::any capture(unsigned idx) const override; |
| |
| /// The source arguments. |
| std::tuple<Args...> const& _tuple; |
| }; |
| |
| template<typename... Args> |
| unsigned |
| ArgTuple<Args...>::count() const { |
| return sizeof...(Args); |
| } |
| |
| template<typename... Args> |
| BufferWriter& |
| ArgTuple<Args...>::print(BufferWriter& w, Spec const& spec, unsigned idx) const { |
| static const auto _fa{ |
| bwf::Get_Arg_Formatter_Array<std::tuple<Args...>>(std::index_sequence_for<Args...>{})}; |
| return _fa[idx](w, spec, _tuple); |
| } |
| |
| template<typename... Args> |
| std::any |
| ArgTuple<Args...>::capture(unsigned idx) const { |
| return {Tuple_Nth(_tuple, idx)}; |
| } |
| |
| } // namespace bwf |
| |
| template<typename Binding, typename Extractor> |
| BufferWriter& |
| BufferWriter::print_nfv(Binding&& names, Extractor&& ex, bwf::ArgPack const& args) { |
| using namespace std::literals; |
| // This gets the actual specifier type from the Extractor - it must be a subclass of @c bwf::Spec |
| // but this enables format extractors to use a subclass if additional data needs to be passed |
| // via the specifier. |
| using spec_type = |
| typename std::remove_reference<decltype(bwf::extractor_spec_type(&std::remove_reference<Extractor>::type::operator()))>::type; |
| int N = args.count(); |
| int arg_idx = 0; // the next argument index to be processed. |
| |
| // Parser is required to return @c false if there's no more data, @c true if something was parsed. |
| while (ex) { |
| std::string_view lit_v; |
| spec_type spec; |
| bool spec_p = ex(lit_v, spec); |
| |
| // If there's a literal, just ship it. |
| if (lit_v.size()) { |
| this->write(lit_v); |
| } |
| |
| if (spec_p) { |
| if (spec._name.size() == 0) { |
| spec._idx = arg_idx++; |
| } |
| |
| while (true) { |
| size_t width = this->remaining(); |
| if (spec._max < width) { |
| width = spec._max; |
| } |
| |
| FixedBufferWriter lw{this->aux_data(), width}; |
| |
| if (0 <= spec._idx) { |
| if (spec._idx < N) { |
| if (spec._type == bwf::Spec::CAPTURE_TYPE) { |
| bwf::arg_capture(ex, lw, spec, args.capture(spec._idx), swoc::meta::CaseArg); |
| } else { |
| args.print(lw, spec, spec._idx); |
| } |
| } else { |
| bwf::Err_Bad_Arg_Index(lw, spec._idx, N); |
| } |
| } else if (spec._name.size()) { |
| names(lw, spec); |
| } |
| if (lw.extent()) { |
| bwf::Adjust_Alignment(lw, spec); |
| if (!this->commit(lw.extent())) { |
| continue; |
| } |
| } |
| break; |
| } |
| } |
| } |
| return *this; |
| } |
| |
| template<typename... Args> |
| BufferWriter& |
| BufferWriter::print(const TextView& fmt, Args&& ... args) { |
| return this->print_nfv(bwf::Global_Names.bind(), bwf::Format::bind(fmt), bwf::ArgTuple{ |
| std::forward_as_tuple(args...)}); |
| } |
| |
| template<typename... Args> |
| BufferWriter& |
| BufferWriter::print(bwf::Format const& fmt, Args&& ... args) { |
| return this->print_nfv(bwf::Global_Names.bind(), fmt.bind(), bwf::ArgTuple{ |
| std::forward_as_tuple(args...)}); |
| } |
| |
| template<typename... Args> |
| BufferWriter& |
| BufferWriter::print_v(TextView const& fmt, std::tuple<Args...> const& args) { |
| return this->print_nfv(bwf::Global_Names.bind(), bwf::Format::bind(fmt), bwf::ArgTuple{args}); |
| } |
| |
| template<typename... Args> |
| BufferWriter& |
| BufferWriter::print_v(const bwf::Format& fmt, const std::tuple<Args...>& args) { |
| return this->print_nfv(bwf::Global_Names.bind(), fmt.bind(), bwf::ArgTuple{args}); |
| } |
| |
| template<typename Binding, typename Extractor> |
| BufferWriter& |
| BufferWriter::print_nfv(Binding const& names, Extractor&& f) { |
| return print_nfv(names, f, bwf::ArgTuple{std::make_tuple()}); |
| } |
| |
| template<typename Binding> |
| BufferWriter& |
| BufferWriter::print_n(Binding const& names, TextView const& fmt) { |
| return print_nfv(names, bwf::Format::bind(fmt), bwf::ArgTuple{std::make_tuple()}); |
| } |
| |
| inline MemSpan<char> |
| BufferWriter::aux_span() { |
| return {this->aux_data(), this->remaining()}; |
| } |
| |
| // ---- Formatting for specific types. |
| |
| // Must be first because it is used by other formatters, and is not inline. |
| BufferWriter& bwformat(BufferWriter& w, bwf::Spec const& spec, std::string_view sv); |
| |
| // Pointers that are not specialized. |
| inline 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); |
| } |
| |
| BufferWriter& bwformat(BufferWriter& w, bwf::Spec const& spec, MemSpan<void> const& span); |
| |
| template<typename T> |
| BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const& spec, MemSpan<T> const& span) { |
| bwf::Spec s{spec}; |
| // If the precision isn't already specified, make it the size of the objects in the span. |
| // This will break the output into blocks of that size. |
| if (spec._prec <= 0) { |
| s._prec = sizeof(T); |
| } |
| return bwformat(w, s, span.template rebind<void>()); |
| } |
| |
| template<size_t N> |
| BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const& spec, const char (& a)[N]) { |
| return bwformat(w, spec, std::string_view(a, N - 1)); |
| } |
| |
| // Capture this explicitly so it doesn't go to any other pointer type. |
| inline BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const& spec, std::nullptr_t) { |
| return bwformat(w, spec, static_cast<void *>(nullptr)); |
| } |
| |
| inline BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const& spec, const char *v) { |
| if (spec._type == 'x' || spec._type == 'X' || spec._type == 'p' || spec._type == 'P') { |
| bwformat(w, spec, static_cast<const void *>(v)); |
| } else if (v != nullptr) { |
| bwformat(w, spec, std::string_view(v)); |
| } else { |
| bwformat(w, spec, nullptr); |
| } |
| return w; |
| } |
| |
| inline BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const& spec, std::string const& s) { |
| return bwformat(w, spec, std::string_view{s}); |
| } |
| |
| inline BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const& spec, TextView tv) { |
| return bwformat(w, spec, static_cast<std::string_view>(tv)); |
| } |
| |
| template<typename X, typename V> |
| BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const&, TransformView<X, V>&& view) { |
| while (view) |
| w.write(char(*(view++))); |
| return w; |
| } |
| |
| template<typename F> |
| auto |
| bwformat(BufferWriter& w, bwf::Spec const& spec, F&& f) -> |
| typename std::enable_if<std::is_floating_point<typename std::remove_reference<F>::type>::value, BufferWriter&>::type { |
| return f < 0 ? bwf::Format_Float(w, spec, -f, true) : bwf::Format_Float(w, spec, f, false); |
| } |
| |
| /* Integer types. |
| |
| Due to some oddities for MacOS building, need a bit more template magic here. The underlying |
| integer rendering is in @c Format_Integer which takes @c intmax_t or @c uintmax_t. For @c |
| bwformat templates are defined, one for signed and one for unsigned. These forward their argument |
| to the internal renderer. To avoid additional ambiguity the template argument is checked with @c |
| std::enable_if to invalidate the overload if the argument type isn't a signed / unsigned |
| integer. One exception to this is @c char which is handled by a previous overload in order to |
| treat the value as a character and not an integer. The overall benefit is this works for any set |
| of integer types, rather tuning and hoping to get just the right set of overloads. |
| */ |
| |
| template<typename I> |
| auto |
| bwformat(BufferWriter& w, bwf::Spec const& spec, I&& i) -> |
| typename std::enable_if<std::is_unsigned<typename std::remove_reference<I>::type>::value && |
| std::is_integral<typename std::remove_reference<I>::type>::value, BufferWriter&>::type { |
| return bwf::Format_Integer(w, spec, i, false); |
| } |
| |
| template<typename I> |
| auto |
| bwformat(BufferWriter& w, bwf::Spec const& spec, I&& i) -> |
| typename std::enable_if<std::is_signed<typename std::remove_reference<I>::type>::value && |
| std::is_integral<typename std::remove_reference<I>::type>::value, BufferWriter&>::type { |
| bool neg_p = false; |
| uintmax_t n = static_cast<uintmax_t>(i); |
| if (i < 0) { |
| n = static_cast<uintmax_t>(-i); |
| neg_p = true; |
| } |
| return bwf::Format_Integer(w, spec, n, neg_p); |
| } |
| |
| inline BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const&, char c) { |
| return w.write(c); |
| } |
| |
| inline BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const& spec, bool f) { |
| using namespace std::literals; |
| if ('s' == spec._type) { |
| w.write(f ? "true"sv : "false"sv); |
| } else if ('S' == spec._type) { |
| w.write(f ? "TRUE"sv : "FALSE"sv); |
| } else { |
| bwf::Format_Integer(w, spec, static_cast<uintmax_t>(f), false); |
| } |
| return w; |
| } |
| |
| // std::string support |
| /** Generate formatted output to a @c std::string @a s using format @a fmt with arguments @a args. |
| * |
| * @tparam Args Format argument types. |
| * @param s Output string. |
| * @param fmt Format string. |
| * @param args A tuple of the format arguments. |
| * @return @a s |
| * |
| * The output is generated to @a s as is. If @a s does not have sufficient space for the output |
| * it is resized to be sufficient and the output formatted again. The result is that @a s will |
| * containing exactly the formatted output. |
| * |
| * @note This function is intended for use by other formatting front ends, such as in classes that |
| * need to generate formatted output. For direct use there is an overload that takes an argument |
| * list. |
| */ |
| template<typename... Args> |
| std::string& |
| bwprint_v(std::string& s, TextView fmt, std::tuple<Args...> const& args) { |
| auto len = s.size(); // remember initial size |
| size_t n = FixedBufferWriter(s.data(), s.size()).print_v(fmt, args).extent(); |
| s.resize(n); // always need to resize - if shorter, must clip pre-existing text. |
| if (n > len) { // dropped data, try again. |
| FixedBufferWriter(s.data(), s.size()).print_v(fmt, args); |
| } |
| return s; |
| } |
| |
| /** Generate formatted output to a @c std::string @a s using format @a fmt with arguments @a args. |
| * |
| * @tparam Args Format argument types. |
| * @param s Output string. |
| * @param fmt Format string. |
| * @param args Arguments for format string. |
| * @return @a s |
| * |
| * The output is generated to @a s as is. If @a s does not have sufficient space for the output |
| * it is resized to be sufficient and the output formatted again. The result is that @a s will |
| * contain exactly the formatted output. |
| * |
| * @note This is intended for direct use. For indirect use (as a backend for another class) see the |
| * overload that takes an argument tuple. |
| */ |
| template<typename... Args> |
| std::string& |
| bwprint(std::string& s, TextView fmt, Args&& ... args) { |
| return bwprint_v(s, fmt, std::forward_as_tuple(args...)); |
| } |
| |
| /// @cond COVARY |
| template<typename... Args> |
| auto |
| FixedBufferWriter::print(TextView fmt, Args&& ... args) -> self_type& { |
| return static_cast<self_type&>(this->super_type::print_v(fmt, std::forward_as_tuple(args...))); |
| } |
| |
| template<typename... Args> |
| auto |
| FixedBufferWriter::print_v(TextView fmt, std::tuple<Args...> const& args) -> self_type& { |
| return static_cast<self_type&>(this->super_type::print_v(fmt, args)); |
| } |
| |
| template<typename... Args> |
| auto |
| FixedBufferWriter::print(bwf::Format const& fmt, Args&& ... args) -> self_type& { |
| return static_cast<self_type&>(this->super_type::print_v(fmt, std::forward_as_tuple(args...))); |
| } |
| |
| template<typename... Args> |
| auto |
| FixedBufferWriter::print_v(bwf::Format const& fmt, std::tuple<Args...> const& args) -> self_type& { |
| return static_cast<self_type&>(this->super_type::print_v(fmt, args)); |
| } |
| |
| /// @endcond |
| |
| // Special case support for @c Scalar, because @c Scalar is a base utility for some other utilities |
| // there can be some unpleasant cirularities if @c Scalar includes BufferWriter formatting. If the |
| // support is here then it's fine because anything using BWF for @c Scalar must include this header. |
| template<intmax_t N, typename C, typename T> class Scalar; |
| namespace detail { |
| template<typename T> |
| auto |
| tag_label(BufferWriter&, const bwf::Spec&, meta::CaseTag<0>) -> void { |
| } |
| |
| template<typename T> |
| auto |
| tag_label(BufferWriter& w, const bwf::Spec& |
| , meta::CaseTag<1>) -> decltype(T::label, meta::TypeFunc<void>()) { |
| w.print("{}", T::label); |
| } |
| } // namespace detail |
| |
| template<intmax_t N, typename C, typename T> |
| BufferWriter& |
| bwformat(BufferWriter& w, bwf::Spec const& spec, Scalar<N, C, T> const& x) { |
| bwformat(w, spec, x.value()); |
| if (!spec.has_numeric_type()) { |
| detail::tag_label<T>(w, spec, meta::CaseArg); |
| } |
| return w; |
| } |
| |
| // Generically a stream operator is a formatter with the default specification. |
| template<typename V> |
| BufferWriter& |
| operator<<(BufferWriter& w, V&& v) { |
| return bwformat(w, bwf::Spec::DEFAULT, std::forward<V>(v)); |
| } |
| |
| // Basic format wrappers - these are here because they're used internally. |
| namespace bwf { |
| /** Hex dump wrapper. |
| * |
| * This wrapper indicates the contained view should be dumped as raw memory in hexadecimal format. |
| * This is intended primarily for internal use by other formatting logic. |
| * |
| * @see As_Hex |
| */ |
| struct HexDump { |
| std::string_view _view; ///< A view of the memory to dump. |
| |
| /** Dump @a n bytes starting at @a mem as hex. |
| * |
| * @param mem First byte of memory to dump. |
| * @param n Number of bytes. |
| */ |
| HexDump(void const *mem, size_t n) : _view(static_cast<char const *>(mem), n) {} |
| }; |
| |
| /** Treat @a t as raw memory and dump the memory as hexadecimal. |
| * |
| * @tparam T Type of argument. |
| * @param t Object to dump. |
| * @return @a A wrapper to do a hex dump. |
| * |
| * This is the standard way to do a hexadecimal memory dump of an object. |
| * |
| * @internal This function exists so that other types can overload it for special processing, |
| * which would not be possible with just @c HexDump. |
| */ |
| template<typename T> |
| HexDump |
| As_Hex(T const& t) { |
| return HexDump(&t, sizeof(T)); |
| } |
| |
| } // namespace bwf |
| |
| BufferWriter& bwformat(BufferWriter& w, bwf::Spec const& spec, bwf::HexDump const& hex); |
| |
| }} // namespace swoc |