code/include/swoc/bwf_base.h (675 lines of code) (raw):

// 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. 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. }; static const Property & Get_Prop() { static const Property prop; return prop; } ///< Character property map. }; /** 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 for any parser that can deliver literals and @c Spec instances. */ struct Format { using self_type = Format; ///< Self reference type. /// Empty format. Format() = default; /// Construct from a format string @a fmt. Format(TextView fmt); /// Move constructor. Format(self_type &&that) = default; /// No copy Format(self_type const &) = delete; /// 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 { MemSpan<Spec const> _fmt; ///< Parsed format string. int _idx = 0; ///< Element index. /// @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); }; /// Wrap the format instance in an extractor. FormatExtractor bind() const; /// @return @c true if all specifiers are literal. bool is_literal() const; using Container = std::vector<Spec>; Container _items; ///< Items from format string. using iterator = Container::iterator; using const_iterator = Container::const_iterator; iterator begin() { return _items.begin(); } iterator end() { return _items.end(); } const_iterator begin() const { return _items.begin(); } const_iterator end() const { return _items.end(); } }; // 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 { public: /// Signature for generators. using Generator = std::function<F>; protected: using Map = std::unordered_map<std::string_view, Generator>; private: using self_type = NameMap; ///< self reference type. public: /// 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); /** Check if a specific name is contained in this mapping. * * @param name Name to check. * @return @c true if present, @c false if not. */ bool contains(std::string_view name); protected: /// Copy @a name in to local storage and return a view of it. std::string_view localize(std::string_view const &name); /// Name to name generator. Map _map; ///< Defined generators. 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. /// Specialized binding for names in an instance of @c ContextNames 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: /** Constructor. * * @param names Names to define for the binding. * @param ctx Binding context. */ 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 class 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>(Get_Prop()._data[static_cast<unsigned>(c)] & Property::ALIGN_MASK); } inline bool Spec::is_sign(char c) { return Get_Prop()._data[static_cast<unsigned>(c)] & Property::SIGN_CHAR; } inline bool Spec::is_type(char c) { return Get_Prop()._data[static_cast<unsigned>(c)] & Property::TYPE_CHAR; } inline bool Spec::is_upper_case_type(char c) { return Get_Prop()._data[static_cast<unsigned>(c)] & Property::UPPER_TYPE_CHAR; } inline bool Spec::is_numeric_type(char c) { return Get_Prop()._data[static_cast<unsigned>(c)] & Property::NUMERIC_TYPE_CHAR; } inline bool Spec::has_numeric_type() const { return Get_Prop()._data[static_cast<unsigned>(_type)] & Property::NUMERIC_TYPE_CHAR; } inline bool Spec::has_upper_case_type() const { return Get_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> bool NameMap<F>::contains(std::string_view name) { return _map.end() != _map.find(name); } template <typename F> std::string_view NameMap<F>::localize(std::string_view const &name) { auto span = _arena.alloc_span<char>(name.size()); memcpy(span, name); return std::string_view(span.data(), span.size()); } 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. /** Output a @c string_view. * * @param w Output * @param spec Format specifier * @param sv View to format. * @return @a w * * @internal 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); /** Format non-specialized pointers. * * @param w Output * @param spec Format specifier. * @param ptr Pointer to format. * @return @a w * * Non-specialized pointers are formatted simply as pointers, rather than the pointed to data. */ BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr); /** Format a generic (void) memory span. * * @param w Output * @param spec Format specifier. * @param span Span to format. * @return @a w * * The format is by default "N:ptr" where N is the size and ptr is a hex formatter pointer. If the * format is "x" or "X" the span content is dumped as contiguous hex. */ BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void const> const &span); /** Format a generic (void) memory span. * * @param w Output * @param spec Format specifier. * @param span Span to format. * @return @a w * * The format is by default "N:ptr" where N is the size and ptr is a hex formatter pointer. If the * format is "x" or "X" the span content is dumped as contiguous hex. * * @internal Overload to avoid unfortunate ambiguities when constructing the span from other spans. * in particular @c MemSpan<char> vs. @c TextView. */ inline BufferWriter & bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void> const &span) { return bwformat(w, spec, span.rebind<void const>()); } 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 const>()); } 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)); } // Char pointer formatting 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; } // doc end 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_v<typename std::remove_reference_t<F>>, 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 const len = s.size(); // remember initial size auto printer = [&]() { return FixedBufferWriter(s.data(), s.capacity()).print_v(fmt, args).extent(); }; size_t n = printer(); s.resize(n); // always need to resize - if shorter, must clip pre-existing text. if (n > len) { // dropped data, try again. printer(); } 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 without resizing, completely replacing any existing text. * 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...)); } /** 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 appended to @a s without resizing. 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 any previous text and the formatted output. */ template <typename... Args> std::string & bwappend(std::string &s, TextView fmt, Args &&...args) { auto const len = s.length(); // Text to preserve. auto const capacity = s.capacity(); // Working space. auto printer = [&]() { return FixedBufferWriter(s.data() + len, s.capacity() - len).print(fmt, args...).extent(); }; // Resize first, otherwise capacity past @a len is cleared on @c resize. s.resize(capacity); auto n = printer() + len; // Get the final length. s.resize(n); // Adjust to correct string length. if (n > capacity) { // dropped data, write it again. printer(); } return s; } /// @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 circularities 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 /** Format a hex dump. * * @param w The output. * @param spec Format specifier. * @param hex Hex dump wrapper. * @return @a w */ BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::HexDump const &hex); /** Format a buffer writer. * * @param w Output buffer, * @param spec Format specifier. * @param ww Input buffer * @return @a w * * This treats @a ww as a view and prints it as text. */ inline BufferWriter & bwformat(BufferWriter &w, bwf::Spec const &spec, BufferWriter const &ww) { return bwformat(w, spec, TextView(ww)); } template <typename T> BufferWriter & BufferWriter::format(bwf::Spec const &spec, T const &t) { return bwformat(*this, spec, t); } template <typename T> BufferWriter & BufferWriter::format(bwf::Spec const &spec, T &&t) { return bwformat(*this, spec, t); } }} // namespace swoc::SWOC_VERSION_NS