/* FORMAT-UTIL.hpp - helpers for formatting and diagnostics Copyright (C) Lumiera.org 2009, 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-util.hpp ** Collection of small helpers and convenience shortcuts for diagnostics & formatting. ** - util::str() performs a failsafe to-String conversion, thereby preferring a ** built-in conversion operator, falling back to just a mangled type string. ** - util::join() generates an enumerating string from elements ** of an arbitrary sequence or iterable. Elements will be passed ** through our [generic string conversion](\ref util::toString) ** ** @see FormatHelper_test ** @see [frontend for boost::format, printf-style](format-string.hpp) ** */ #ifndef LIB_FORMAT_UTIL_H #define LIB_FORMAT_UTIL_H #include "lib/hash-standard.hpp" #include "lib/meta/trait.hpp" #include "lib/format-obj.hpp" #include "lib/itertools.hpp" #include "lib/symbol.hpp" #include "lib/util.hpp" #include #include #include #include #include #include #include namespace util { using boost::enable_if; using lib::meta::can_convertToString; using lib::meta::can_lexical2string; using lib::meta::can_IterForEach; using lib::Symbol; using util::removePrefix; using util::removeSuffix; using util::isnil; using std::string; using std::move; namespace { // we need to guard the string conversion // to avoid a compiler error in case the type isn't convertible.... // precision for rendering of double values const auto DIAGNOSTICS_DOUBLE_PRECISION = 8; const auto DIAGNOSTICS_FLOAT_PRECISION = 5; template struct use_StringConversion : can_convertToString { }; template struct use_LexicalConversion { enum { value = can_lexical2string::value && !can_convertToString::value }; }; /** helper: reliably get some string representation for type X */ template struct _InvokeFailsafe { static string toString (X const&) { return ""; } }; template struct _InvokeFailsafe >::type> { static string toString (X const& val) try { return string(val); } catch(...) { return ""; } }; template struct _InvokeFailsafe >::type> { static string toString (X const& val) try { return boost::lexical_cast (val); } catch(...) { return ""; } }; /** explicit specialisation to control precision of double values. * @note we set an explicit precision, since this is a diagnostic facility * and we typically do not want to see all digits, but, for test code, * we do want a predictable string representation of simple fractional * values like `0.1` (which can not be represented as binary floats) */ template<> struct _InvokeFailsafe { static string toString (double const& val) try { std::ostringstream buffer; buffer.precision(DIAGNOSTICS_DOUBLE_PRECISION); buffer << val; return buffer.str(); } catch(...) { return ""; } }; template<> struct _InvokeFailsafe { static string toString (float const& val) try { std::ostringstream buffer; buffer.precision(DIAGNOSTICS_FLOAT_PRECISION); buffer << val; return buffer.str(); } catch(...) { return ""; } }; }//(End) guards/helpers /** try to get an object converted to string. * A custom/standard conversion to string is used, * if applicable; otherwise, some standard types can be * converted by a lexical_cast (based on operator<< ). * Otherwise, either the fallback string is used, or just * a string based on the (mangled) type. */ template inline string str ( TY const& val , Symbol prefix="" ///< prefix to prepend in case conversion is possible , Symbol fallback =0 /// < replacement text to show if string conversion fails ) { string res = _InvokeFailsafe::toString(val); if (!isnil (res)) return string(prefix) + res; else return fallback? string(fallback) : "«"+typeStr(val)+"»"; } namespace { // helper to convert arbitrary elements toString template inline void do_stringify(CON&) { /* do nothing */ } template inline void do_stringify(CON& container, X const& elm, ELMS const& ...args) { container += util::str(elm); do_stringify (container, args...); } template struct SeqContainer : CON { void operator+= (string&& s) { CON::push_back (move(s)); } }; // most common case: use a vector container... using std::vector; template struct SeqContainer, ELMS...> :vector { SeqContainer() { this->reserve(sizeof...(ELMS)); } void operator+= (string&& s) { this->emplace_back (move(s)); } }; }//(end) stringify helper /** convert a sequence of elements to string * @param elms sequence of arbitrary elements * @return a collection of type CON, initialised by the * string representation of the given elements */ template inline CON stringify(ELMS const& ...elms) { SeqContainer storage; do_stringify (storage, elms...); return CON {move(storage)}; } namespace { // helper to build range iterator on demand template struct _RangeIter { using StlIter = typename CON::const_iterator; lib::RangeIter iter; _RangeIter(CON const& collection) : iter(begin(collection), end(collection)) { } }; template struct _RangeIter >::type> { IT iter; _RangeIter(IT&& srcIter) : iter(std::forward(srcIter)) { } _RangeIter(IT const& srcIter) : iter(srcIter) { } }; }//(end) join helper /** * enumerate a collection's contents, separated by delimiter. * @param coll something that is standard-iterable * @return all contents converted to string and joined into * a single string, with separators interspersed. * @remarks based `ostringstream`; additionally, we use our * [failsafe string conversion](\ref util::str), * which in turn invokes custom string conversion, * or lexical_cast as appropriate. * @remarks alternatively, the `boost::join` library function * could be used, which works on _arbitrary sequences_, * which incurs some additional weight (both in terms * of header include and debug code size). And failures * on template substitution tend to be hard to understand, * since this _generic sequence_ concept is just so danm * absolutely generic (In fact that was the reason why I * gave up and just rolled our own `join` utility) */ template inline string join (CON&& coll, string const& delim =", ") { using Coll = typename lib::meta::Strip::Type; using Val = typename Coll::value_type; std::function toString = [] (Val const& val) { return str(val); }; _RangeIter range(std::forward(coll)); auto strings = lib::transformIterator(range.iter, toString); if (!strings) return ""; std::ostringstream buffer; for (string const& elm : strings) buffer << elm << delim; // chop off last delimiter size_t len = buffer.str().length(); ASSERT (len > delim.length()); return buffer.str().substr(0, len - delim.length()); } } // namespace util #endif /*LIB_FORMAT_UTIL_H*/