formatting wrapper/frontend: unit test pass.

Closes #166
This commit is contained in:
Fischlurch 2011-12-31 06:38:31 +01:00
parent e1b9b5b135
commit 37384f1b68
5 changed files with 142 additions and 31 deletions

View file

@ -24,11 +24,13 @@
** Implementation for printf-style formatting, based on boost::format.
** This file holds the generic implementation of our format frontend,
** which actually just invokes boost::format. The corresponding header
** format-string.hpp contains some template functions and classes,
** which select an appropriate wrapper to pass the calls down.
** format-string.hpp provides some template functions and classes,
** either invoking a custom string conversion, or passing primitive
** values down unaltered.
**
** Here, we define explicit specialisations for the frontend to invoke,
** which in turn just pass on the given argument value to the embedded
** boost::format object, which in turn integrates the formatted result
** boost::format object, which in turn dumps the formatted result
** into an embedded string stream.
**
** To avoid exposing boost::format in the frontend header, we use an
@ -61,6 +63,7 @@ namespace util {
using boost::format;
namespace { // implementation details...
inline boost::format&
@ -78,8 +81,7 @@ namespace util {
/** in case the formatting of a (primitive) value fails,
* we try to use a error indicator instead
*/
* we try to supply an error indicator instead */
void
pushFailsafeReplacement (char* formatter, const char* errorMsg =NULL)
try {
@ -109,14 +111,26 @@ namespace util {
/** */
/** Build a formatter object based on the given format string.
* The actual implementation is delegated to an boost::format object,
* which is placement-constructed into an opaque buffer embedded into
* this object. Defining the necessary size for this buffer relies
* on a implementation details of boost::format (and might break)
*/
_Fmt::_Fmt (string formatString)
{
BOOST_STATIC_ASSERT (sizeof(boost::format) <= FORMATTER_SIZE);
new(formatter_) boost::format(formatString);
suppressInsufficientArgumentErrors (formatter_);
}
try {
BOOST_STATIC_ASSERT (sizeof(boost::format) <= FORMATTER_SIZE);
new(formatter_) boost::format(formatString);
suppressInsufficientArgumentErrors (formatter_);
}
catch (boost::io::bad_format_string& syntaxError)
{
throw lumiera::error::Fatal (syntaxError
, _Fmt("Format string '%s' is broken") % formatString
, LUMIERA_ERROR_FORMAT_SYNTAX);
}
_Fmt::~_Fmt ()
{
@ -152,6 +166,7 @@ namespace util {
}
catch (std::exception& failure)
{
_clear_errorflag();
WARN (progress, "Format: Parameter '%s' causes problems: %s"
, cStr(str(val))
, failure.what());
@ -159,6 +174,7 @@ namespace util {
}
catch (...)
{
_clear_errorflag();
WARN (progress, "Format: Unexpected problems accepting format parameter '%s'", cStr(str(val)));
pushFailsafeReplacement (formatter);
}
@ -198,11 +214,13 @@ namespace util {
catch (std::exception& failure)
{
_clear_errorflag();
WARN (progress, "Format: Failure to receive formatted result: %s", failure.what());
return "<formatting failure>";
}
catch (...)
{
_clear_errorflag();
WARN (progress, "Format: Unexpected problems while formatting output.");
return "<unexpected problems>";
}
@ -222,15 +240,20 @@ namespace util {
catch(std::exception& failure)
{
_clear_errorflag();
WARN (progress, "Format: Failure when outputting formatted result: %s", failure.what());
return os << "<formatting failure>";
}
catch(...)
{
_clear_errorflag();
WARN (progress, "Format: Unexpected problems while producing formatted output.");
return os << "<unexpected problems>";
}
LUMIERA_ERROR_DEFINE (FORMAT_SYNTAX, "Syntax error in format string for boost::format");
} // namespace util

View file

@ -25,19 +25,77 @@
** 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 conversion to string and catches any exceptions.
** 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).
**
** @remarks The implementation is invoked through a set of explicit specialisations.
** For custom types, we prefer to invoke operator string(), which is determined
** by a directly coded metaprogramming test. The compile time and object size
** overhead incurred by using this header was verified to be negligible.
** \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-helper
** @see format-util.hpp
**
*/
@ -71,26 +129,33 @@ namespace std { // forward declaration to avoid including <iostream>
namespace util {
typedef unsigned char uchar;
using boost::enable_if;
using std::string;
using boost::enable_if;
typedef unsigned char uchar;
LUMIERA_ERROR_DECLARE (FORMAT_SYNTAX); ///< "Syntax error in format string for boost::format"
/**
* @todo write type comment
* 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 internal implementation Buffer */
/** 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_;
@ -143,9 +208,10 @@ namespace util {
* \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. 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
* 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.
@ -203,9 +269,17 @@ namespace util {
};
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("<string conversion failed: ")+ex.what()+">";
@ -276,7 +350,7 @@ namespace util {
}
};
/** some custom types explicitly provide string representation */
/** some custom types explicitly provide a string representation */
template<typename VAL>
struct _Fmt::Converter<VAL, typename enable_if< _shall_convert_toString<VAL> >::type>
{

View file

@ -329,7 +329,18 @@ return: 0
END
PLANNED "formatting by string template" FormatString_test <<END
TEST "formatting by string template" FormatString_test <<END
out-lit: --format-template--int=0012--double=+1.23--string=Lumiera --
out: 0x....+ _____ .
out: «.+util.+test.+Silent.»
out-lit: __nix_
out-lit: ____
out-lit: __1__
out-lit: __1__
out-lit: __↯__
out-lit: __dirt__
out-lit: __1234__
out-lit: __0xff__
return: 0
END
@ -394,7 +405,7 @@ return: 0
END
TEST "metaprogramming helpers" Metautils_test <<END
TEST "metaprogramming helpers" MetaUtils_test <<END
return: 0
END

View file

@ -22,6 +22,7 @@
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/format-string.hpp"
#include "lib/error.hpp"
#include "lib/util.hpp"
@ -263,6 +264,8 @@ namespace test {
cout << _Fmt("__%d__") % "dirt" << endl;
cout << _Fmt("__%d__") % "1234" << endl;
cout << _Fmt("__%d__") % "0xff" << endl;
VERIFY_ERROR(FORMAT_SYNTAX, _Fmt("%broken"));
}

View file

@ -88,7 +88,7 @@ namespace test{
FixedFrameQuantiser fixQ(25);
uint frames = (rand() % MAX_FRAMES);
FSecs dirt = (F25 / (1 + rand() % DIRT_GRAIN));
FSecs dirt = (F25 / (2 + rand() % DIRT_GRAIN));
Time rawTime (dirt + frames*F25);