diff --git a/libuavcan/include/uavcan/marshal/char_array_formatter.hpp b/libuavcan/include/uavcan/marshal/char_array_formatter.hpp new file mode 100644 index 0000000000..a0b2c7aac6 --- /dev/null +++ b/libuavcan/include/uavcan/marshal/char_array_formatter.hpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2014 Pavel Kirienko + */ + +#pragma once + +#include +#include + +#if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11) +# error UAVCAN_CPP_VERSION +#endif + +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 +# include +#endif + +namespace uavcan +{ + +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + +template +class CharArrayFormatter +{ + ArrayType_& array_; + + template + typename std::enable_if::value>::type + writeValue(T value) + { + array_.template appendFormatted("%f", double(value)); + } + + template + typename std::enable_if::value>::type + writeValue(T value) + { + if (std::is_same()) + { + array_.template appendFormatted("%c", value); // TODO: speedup + } + else if (std::is_signed()) + { + array_.template appendFormatted("%lli", static_cast(value)); + } + else + { + array_.template appendFormatted("%llu", static_cast(value)); + } + } + + template + typename std::enable_if::value && !std::is_same::value>::type + writeValue(T value) + { + array_.template appendFormatted("%p", static_cast(value)); + } + + void writeValue(const char* value) + { + array_.template appendFormatted("%s", value); + } + +public: + typedef ArrayType_ ArrayType; + + CharArrayFormatter(ArrayType& array) + : array_(array) + { } + + ArrayType& getArray() { return array_; } + const ArrayType& getArray() const { return array_; } + + void write(const char* text) + { + writeValue(text); + } + + template + void write(const char* s, T value, Args... args) + { + while (s && *s) + { + if (*s == '%' && *++s != '%') + { + writeValue(value); + write(++s, args...); + break; + } + writeValue(*s++); + } + } +}; + +#else + +template +class CharArrayFormatter +{ + ArrayType_& array_; + +public: + typedef ArrayType_ ArrayType; + + CharArrayFormatter(ArrayType& array) + : array_(array) + { } + + ArrayType& getArray() { return array_; } + const ArrayType& getArray() const { return array_; } + + void write(const char* const text) + { + array_.template appendFormatted("%s", text); + } + + /** + * This version does not support more than one formatted argument, though it can be improved. + * And it is unsafe. + * There is typesafe version for C++11 above. + * TODO: make this version typesafe and add support for multiple args. + */ + template + void write(const char* const format, const A value) + { + array_.template appendFormatted(format, value); + } +}; + +#endif + +} diff --git a/libuavcan/test/marshal/char_array_formatter.cpp b/libuavcan/test/marshal/char_array_formatter.cpp new file mode 100644 index 0000000000..9bf8a6c9f2 --- /dev/null +++ b/libuavcan/test/marshal/char_array_formatter.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 Pavel Kirienko + */ + +#include +#include + +using uavcan::Array; +using uavcan::ArrayModeDynamic; +using uavcan::ArrayModeStatic; +using uavcan::IntegerSpec; +using uavcan::SignednessSigned; +using uavcan::SignednessUnsigned; +using uavcan::CastModeSaturate; +using uavcan::CastModeTruncate; +using uavcan::CharArrayFormatter; + +TEST(CharArrayFormatter, Basic) +{ + typedef Array, ArrayModeDynamic, 45> A8; + A8 a; + + CharArrayFormatter f(a); + ASSERT_STREQ("", f.getArray().c_str()); + + f.write("Don't %s.", "Panic"); + ASSERT_STREQ("Don't Panic.", f.getArray().c_str()); + + f.write(" abc%idef ", 123); + ASSERT_STREQ("Don't Panic. abc123def ", f.getArray().c_str()); + + f.write("%f", 0.0); + ASSERT_STREQ("Don't Panic. abc123def 0.000000", f.getArray().c_str()); + + a.clear(); + ASSERT_STREQ("", f.getArray().c_str()); + + f.write("123456789"); + ASSERT_STREQ("123456789", f.getArray().c_str()); +} + +#if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11) +# error UAVCAN_CPP_VERSION +#endif + +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + +TEST(CharArrayFormatter, Hardcore) +{ + typedef Array, ArrayModeDynamic, 150> A8; + A8 a; + + CharArrayFormatter f(a); + + f.write("%% char='%*' double='%*' long='%*' unsigned long='%*' int='%s' long double='%*' bool='%*' const char='%*' %%", + '%', -12.3456, -123456789123456789L, 987654321, -123456789, 0.000001L, true, "Don't Panic."); + + static const std::string Reference = + "% char='%' double='-12.345600' long='-123456789123456789' unsigned long='987654321' int='-123456789' " + "long double='0.000001' bool='1' const char='Don't"; // 8 chars truncated! + + ASSERT_STREQ(Reference.c_str(), f.getArray().c_str()); + + a.clear(); + + f.write(""); + ASSERT_STREQ("", f.getArray().c_str()); + + f.write("%%"); // Nothing to format --> "%%" is not expanded + ASSERT_STREQ("%%", f.getArray().c_str()); + + f.write("%*", "Test", 123, true); // Extra args ignored + ASSERT_STREQ("%%Test", f.getArray().c_str()); + + f.write("%% %* %* %% %*", true); // Insufficient args are OK; second "%%" is not expanded + ASSERT_STREQ("%%Test% 1 %* %% %*", f.getArray().c_str()); +} + +#endif