diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f72825..56e4411 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,9 @@ set (HEADERS kaitai/kaitaistream.h kaitai/kaitaistruct.h kaitai/exceptions.h + # required by public header kaitai/kaitaistream.h, so I guess it must be included as well + # (see https://stackoverflow.com/a/61152762) + kaitai/detail/make_unsigned.h ) set (SOURCES diff --git a/kaitai/detail/make_unsigned.h b/kaitai/detail/make_unsigned.h new file mode 100644 index 0000000..89c0066 --- /dev/null +++ b/kaitai/detail/make_unsigned.h @@ -0,0 +1,19 @@ +#ifndef KAITAI_DETAIL_MAKE_UNSIGNED_H +#define KAITAI_DETAIL_MAKE_UNSIGNED_H + +namespace kaitai { + // poor man's implementation of `std::make_unsigned` in pre-C++11 versions + // (https://stackoverflow.com/a/27577556) + // + // Don't use this externally. This is an internal implementation helper + // and may be removed at any time. + template struct make_unsigned { typedef T type; }; + template<> struct make_unsigned { typedef unsigned char type; }; + template<> struct make_unsigned { typedef unsigned char type; }; + template<> struct make_unsigned { typedef unsigned short type; }; + template<> struct make_unsigned { typedef unsigned int type; }; + template<> struct make_unsigned { typedef unsigned long type; }; + // `long long` is available only since C++11 +} + +#endif diff --git a/kaitai/exceptions.h b/kaitai/exceptions.h index 5c09c46..9591843 100644 --- a/kaitai/exceptions.h +++ b/kaitai/exceptions.h @@ -60,7 +60,7 @@ class undecided_endianness_error: public kstruct_error { class validation_failed_error: public kstruct_error { public: validation_failed_error(const std::string what, kstream* io, const std::string src_path): - kstruct_error("at pos " + kstream::to_string(static_cast(io->pos())) + ": validation failed: " + what, src_path), + kstruct_error("at pos " + kstream::to_string(io->pos()) + ": validation failed: " + what, src_path), m_io(io) { } diff --git a/kaitai/kaitaistream.cpp b/kaitai/kaitaistream.cpp index 47fc604..fb8be3d 100644 --- a/kaitai/kaitaistream.cpp +++ b/kaitai/kaitaistream.cpp @@ -566,22 +566,6 @@ int kaitai::kstream::mod(int a, int b) { return r; } -#include -std::string kaitai::kstream::to_string(int val) { - // if int is 32 bits, "-2147483648" is the longest string representation - // => 11 chars + zero => 12 chars - // if int is 64 bits, "-9223372036854775808" is the longest - // => 20 chars + zero => 21 chars - char buf[25]; - int got_len = snprintf(buf, sizeof(buf), "%d", val); - - // should never happen, but check nonetheless - if (got_len > sizeof(buf)) - throw std::invalid_argument("to_string: integer is longer than string buffer"); - - return std::string(buf); -} - #include std::string kaitai::kstream::reverse(std::string val) { std::reverse(val.begin(), val.end()); diff --git a/kaitai/kaitaistream.h b/kaitai/kaitaistream.h index 786472c..e9229fd 100644 --- a/kaitai/kaitaistream.h +++ b/kaitai/kaitaistream.h @@ -8,6 +8,15 @@ #include #include #include +#include +#include + +// https://stackoverflow.com/a/40512515 +#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) +#define KAITAI_STREAM_H_CPP11_SUPPORT +#else +#include +#endif namespace kaitai { @@ -224,7 +233,34 @@ class kstream { * Should be used in place of std::to_string() (which is available only * since C++11) in older C++ implementations. */ - static std::string to_string(int val); + template +#ifdef KAITAI_STREAM_H_CPP11_SUPPORT + // https://stackoverflow.com/a/27913885 + typename std::enable_if< + std::is_integral::value, + std::string + >::type +#else + std::string +#endif + static to_string(I val) { +#ifdef KAITAI_STREAM_H_CPP11_SUPPORT + typedef typename std::make_unsigned::type U; +#else + typedef typename make_unsigned::type U; +#endif + // in theory, `digits10 + 3` would be enough (minus sign + leading digit + // + null terminator), but let's add a little more to be safe + char buf[std::numeric_limits::digits10 + 5]; + if (val < 0) { + buf[0] = '-'; + // get absolute value without undefined behavior (https://stackoverflow.com/a/12231604) + unsigned_to_decimal(-static_cast(val), &buf[1]); + } else { + unsigned_to_decimal(val, buf); + } + return std::string(buf); + } /** * Reverses given string `val`, so that the first character becomes the @@ -258,6 +294,31 @@ class kstream { void init(); void exceptions_enable() const; + template +#ifdef KAITAI_STREAM_H_CPP11_SUPPORT + typename std::enable_if< + std::is_integral::value && std::is_unsigned::value, + void + >::type +#else + void +#endif + static inline unsigned_to_decimal(U number, char *buffer) { + // Implementation from https://ideone.com/nrQfA8 by Alf P. Steinbach + // (see https://www.zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html#comment-1033931478) + if (number == 0) { + *buffer++ = '0'; + } else { + char *p_first = buffer; + while (number != 0) { + *buffer++ = static_cast('0' + number % 10); + number /= 10; + } + std::reverse(p_first, buffer); + } + *buffer = '\0'; + } + static const int ZLIB_BUF_SIZE = 128 * 1024; };