/* FORMAT-STRING.hpp - string template formatting based on boost::format Copyright (C) Lumiera.org 2011, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file format-string.hpp ** Front-end for printf-style string template interpolation. ** While the actual implementation just delegates to boost::format, this front-end ** hides the direct dependency, additionally invokes a custom toSting conversion ** whenever possible, provides a direct automatic conversion to the formatted result ** to string and catches any exceptions. ** ** This front-end is used pervasively for diagnostics and logging, so keeping down the ** compilation and object size cost and reliably handling any error is more important ** than the (small) performance gain of directly invoking boost::format (which is ** known to be 10 times slower than printf anyway). ** ** \par Implementation notes ** To perform the formatting, usually a \c _Fmt object is created as an anonymous ** temporary, but it may as well be stored into a variable. Copying is not permitted. ** Individual parameters are then fed for formatting through the \c '%' operator. ** Each instance of _Fmt uses its own embedded boost::format object for implementation, ** but this formatter resides within an opaque buffer embedded into the frontend object. ** The rationale for this admittedly tricky approach is to confine any usage of boost::format ** to the implementation translation unit (format-string.cpp). ** ** The implementation is invoked by the frontend through a set of explicit specialisations ** for all the relevant \em primitive data types. For custom types, we prefer to invoke ** operator string() if possible, which is determined by a simple metaprogramming test, ** which is defined in lib/meta/util.pp, without relying on boost. As a fallback, for ** all other types without built-in or custom string conversion, we use the mangled ** type string produced by RTTI. ** ** The compile time and object size overhead incurred by using this header was verified ** to be negligible, in comparison to using boost::format. When compiling a demo example ** on x86_64, the following executable sizes could be observed: ** ** debug stripped ** just string concatenation ............... 42k 8.8k ** including and using format-string.hpp ... 50k 9.4k ** including and using boost::format ....... 420k 140k ** ** In addition, we need to take the implementation translation unit (format-string.cpp) ** into account, which is required once per application and contains the specialisations ** for all primitive types. In the test showed above, the corresponding object file ** had a size of 1300k (with debug information) resp. 290k (stripped). ** ** \par Usage ** The syntax of the format string is defined by boost::format and closely mimics ** the printf formatting directives. The notable difference is that boost::format ** uses the C++ stream output framework, and thus avoiding the perils of printf. ** The individual formatting placeholders just set the corresponding flags on ** an embedded string stream, thus the actual parameter types cause the ** selection of a suitable format, not the definitions within the ** format string. ** ** An illegal format string will raise an error::Fatal. Any other error during usage of ** the formatter is caught, logged and suppressed, inserting an error indicator into ** the formatted result instead ** ** A formatter is usually created as an anonymous object, at places where a string ** is expected. An arbitrary number of parameters is then supplied using the \c '%' operator. ** The result can be obtained ** - by string conversion ** - by feeding into an output stream. ** ** Code example: ** \code ** double total = 22.9499; ** const char * currency = "€"; ** cout << _Fmt("price %+5.2f %s") % total % currency << endl; ** \endcode ** ** @remarks See the unit-test for extensive usage examples and corner cases. ** The header format-util.hpp provides an alternative string conversion, ** using a bit of boost type traits and lexical_cast, but no boost::format. ** @warning not suited for performance critical code. About 10 times slower than printf. ** ** @see FormatString_test ** @see format-util.hpp ** */ #ifndef UTIL_FORMAT_STRING_H #define UTIL_FORMAT_STRING_H #include "lib/error.hpp" #include "lib/meta/util.hpp" #include "lib/meta/size-trait.hpp" #include #include #include #include namespace std { // forward declaration to avoid including template class char_traits; template class basic_ostream; typedef basic_ostream > ostream; } namespace util { using std::string; using boost::enable_if; typedef unsigned char uchar; LUMIERA_ERROR_DECLARE (FORMAT_SYNTAX); ///< "Syntax error in format string for boost::format" /** * A front-end for using printf-style formatting. * Values to be formatted can be supplied through the * operator%. Custom defined string conversions on objects * will be used, any errors while invoking the format operation * will be suppressed. The implementation is based on boost::format, * but kept opaque to keep code size and compilation times down. * @see FormatString_test */ class _Fmt : boost::noncopyable { /** size of an opaque implementation Buffer */ enum{ FORMATTER_SIZE = lib::meta::SizeTrait::BOOST_FORMAT }; typedef char Implementation[FORMATTER_SIZE]; /** @internal buffer to hold a boost::format */ mutable Implementation formatter_; template static void format (const VAL, Implementation&); /** helper to prepare parameters for inclusion */ template struct Converter; public: ~_Fmt (); _Fmt (string formatString); operator string() const; ///< get the formatted result template _Fmt& operator% (VAL const&); friend std::ostream& operator<< (std::ostream& os, _Fmt const&); friend bool operator== (_Fmt const&, _Fmt const&); friend bool operator== (_Fmt const&, string const&); friend bool operator== (_Fmt const&, const char * const); friend bool operator== (string const& , _Fmt const&); friend bool operator== (const char * const, _Fmt const&); template friend bool operator != (_Fmt const& fmt, X const& x) { return !(fmt == x); } template friend bool operator != (X const& x, _Fmt const& fmt) { return !(x == fmt); } }; /* ===== forwarding into the implementation ====== */ /** The percent operator (\c '%' ) is used do feed parameter values * to be included into the formatted result, at the positions marked * by printf-style placeholders within the format string. * * \par type specific treatment * Basic types (numbers, chars, strings) are passed to the implementation * (= boost::format) literally. For custom types, we try to use a custom * string conversion, if applicable. Non-NULL pointers will be dereferenced, * with the exception of C-Strings and \c void*. Any other type gets just * translated into a type-ID (using the mangled RTTI info). In case of errors * during the conversion, a string representation of the error is returned * @param val arbitrary value or pointer to be included into the result * @warning you need to provide exactly the right number of parameters, * i.e. matching the number of fields in the format string. * @note EX_FREE */ template inline _Fmt& _Fmt::operator% (VAL const& val) { Converter::dump (val, formatter_); return *this; } namespace { // helpers to pick a suitable specialisation.... /** * by default we don't allow to * treat any types directly by boost::format. * As fallback we rather just produce a type-ID */ template struct _allow_call { enum{ value = false };}; /* the following definitions enable some primitive types * to be handed over to the boost::format implementation */ template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call{ enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template<> struct _allow_call { enum{ value = true }; }; template struct _shall_format_directly { typedef typename lib::meta::UnConst::Type BaseType; enum{ value = _allow_call::value }; }; template struct _shall_convert_toString { enum{ value = ! _shall_format_directly::value && lib::meta::can_convertToString::value }; }; inline void _clear_errorflag() { const char* errID = lumiera_error(); TRACE_IF (errID, progress, "Lumiera errorstate '%s' cleared.", errID); } inline string _log_and_stringify (std::exception const& ex) { _clear_errorflag(); WARN (progress, "Error while invoking custom string conversion: %s", ex.what()); try { return string(""; } catch(...) { /* secondary errors ignored */ } return "(formatting failure)"; } inline string _log_unknown_exception() { const char* errID = lumiera_error(); if (errID) ERROR (progress, "Unknown error while invoking custom string conversion. Lumiera error flag = %s", errID); else ERROR (progress, "Unknown error while invoking custom string conversion. No Lumiera error flag set."); return ""; } }//(End) guards/helpers /* === explicit specialisations to control the kind of conversion === */ /** default/fallback: just indicate the (static) type */ template struct _Fmt::Converter { static void dump (VAL const&, Implementation& impl) { format (string("«")+typeid(VAL).name()+"»", impl); } }; template struct _Fmt::Converter { static void dump (const VAL *pVal, Implementation& impl) { if (pVal) Converter::dump(*pVal, impl); else format ("", impl); } }; template<> struct _Fmt::Converter { static void dump (const void* address, Implementation& impl) { format (address, impl); } }; template<> struct _Fmt::Converter { static void dump (const char* cString, Implementation& impl) { format (cString? cString : "↯", impl); } }; /** some custom types explicitly provide a string representation */ template struct _Fmt::Converter >::type> { static void dump (VAL const& val, Implementation& impl) try { format (string(val), impl); } catch(std::exception const& ex) { format (_log_and_stringify(ex), impl); } catch(...) { format (_log_unknown_exception(), impl); } }; /** some basic types are directly forwarded down to the implementation; * @note this requires explicit specialisations in format-string.cpp */ template struct _Fmt::Converter >::type> { static void dump (const VAL val, Implementation& impl) { format (val, impl); } }; /* === comparison of formatter objects === */ inline bool operator== (_Fmt const& left, _Fmt const& right) { return string(left) == string(right); } inline bool operator== (_Fmt const& fmt, string const& str) { return string(fmt) == str; } inline bool operator== (_Fmt const& fmt, const char * const cString) { return string(fmt) == string(cString); } inline bool operator== (string const& str, _Fmt const& fmt) { return fmt == str; } inline bool operator== (const char * const cString, _Fmt const& fmt) { return fmt == cString; } } // namespace util #endif