From 1a0ffe372f4da8408c5d08a36013536a3396b9e6 Mon Sep 17 00:00:00 2001 From: Mehmet Samet Duman Date: Thu, 2 Apr 2026 17:35:01 +0300 Subject: NOISSUE reformat libnbtplusplus to new MeshMC clang format rules Signed-off-by: Mehmet Samet Duman --- include/crtp_tag.h | 71 ++- include/endian_str.h | 132 +++--- include/io/izlibstream.h | 120 ++--- include/io/ozlibstream.h | 131 +++--- include/io/stream_reader.h | 213 ++++----- include/io/stream_writer.h | 195 ++++---- include/io/zlib_streambuf.h | 63 ++- include/make_unique.h | 14 +- include/nbt_visitor.h | 94 ++-- include/primitive_detail.h | 48 +- include/tag.h | 274 +++++------ include/tag_array.h | 467 ++++++++++--------- include/tag_compound.h | 257 ++++++----- include/tag_list.h | 416 +++++++++-------- include/tag_primitive.h | 170 ++++--- include/tag_string.h | 101 +++-- include/tagfwd.h | 32 +- include/text/json_formatter.h | 31 +- include/value.h | 408 +++++++++-------- include/value_initializer.h | 72 +-- src/endian_str.cpp | 518 ++++++++++----------- src/io/izlibstream.cpp | 123 +++-- src/io/ozlibstream.cpp | 155 +++---- src/io/stream_reader.cpp | 155 +++---- src/io/stream_writer.cpp | 53 +-- src/tag.cpp | 300 ++++++------ src/tag_compound.cpp | 138 +++--- src/tag_list.cpp | 278 +++++++----- src/tag_string.cpp | 29 +- src/text/json_formatter.cpp | 426 +++++++++--------- src/value.cpp | 576 +++++++++++------------ src/value_initializer.cpp | 29 +- test/endian_str_test.h | 278 ++++++------ test/format_test.cpp | 119 ++--- test/nbttest.h | 1002 ++++++++++++++++++++++------------------- test/read_test.h | 448 +++++++++--------- test/test_value.h | 44 +- test/write_test.h | 451 +++++++++---------- test/zlibstream_test.h | 453 ++++++++++--------- 39 files changed, 4695 insertions(+), 4189 deletions(-) diff --git a/include/crtp_tag.h b/include/crtp_tag.h index 28a59f1530..91c629732d 100644 --- a/include/crtp_tag.h +++ b/include/crtp_tag.h @@ -33,38 +33,63 @@ namespace nbt { -namespace detail -{ + namespace detail + { - template - class crtp_tag : public tag - { - public: - //Pure virtual destructor to make the class abstract - virtual ~crtp_tag() noexcept = 0; + template class crtp_tag : public tag + { + public: + // Pure virtual destructor to make the class abstract + virtual ~crtp_tag() noexcept = 0; - tag_type get_type() const noexcept override final { return Sub::type; }; + tag_type get_type() const noexcept override final + { + return Sub::type; + }; - std::unique_ptr clone() const& override final { return make_unique(sub_this()); } - std::unique_ptr move_clone() && override final { return make_unique(std::move(sub_this())); } + std::unique_ptr clone() const& override final + { + return make_unique(sub_this()); + } + std::unique_ptr move_clone() && override final + { + return make_unique(std::move(sub_this())); + } - tag& assign(tag&& rhs) override final { return sub_this() = dynamic_cast(rhs); } + tag& assign(tag&& rhs) override final + { + return sub_this() = dynamic_cast(rhs); + } - void accept(nbt_visitor& visitor) override final { visitor.visit(sub_this()); } - void accept(const_nbt_visitor& visitor) const override final { visitor.visit(sub_this()); } + void accept(nbt_visitor& visitor) override final + { + visitor.visit(sub_this()); + } + void accept(const_nbt_visitor& visitor) const override final + { + visitor.visit(sub_this()); + } - private: - bool equals(const tag& rhs) const override final { return sub_this() == static_cast(rhs); } + private: + bool equals(const tag& rhs) const override final + { + return sub_this() == static_cast(rhs); + } - Sub& sub_this() { return static_cast(*this); } - const Sub& sub_this() const { return static_cast(*this); } - }; + Sub& sub_this() + { + return static_cast(*this); + } + const Sub& sub_this() const + { + return static_cast(*this); + } + }; - template - crtp_tag::~crtp_tag() noexcept {} + template crtp_tag::~crtp_tag() noexcept {} -} + } // namespace detail -} +} // namespace nbt #endif // CRTP_TAG_H_INCLUDED diff --git a/include/endian_str.h b/include/endian_str.h index 4a3500342b..a21a87f94b 100644 --- a/include/endian_str.h +++ b/include/endian_str.h @@ -37,82 +37,78 @@ namespace endian { -enum endian { little, big }; + enum endian { little, big }; -///Reads number from stream in specified endian -template -void read(std::istream& is, T& x, endian e); + /// Reads number from stream in specified endian + template void read(std::istream& is, T& x, endian e); -///Reads number from stream in little endian -NBT_EXPORT void read_little(std::istream& is, uint8_t& x); -NBT_EXPORT void read_little(std::istream& is, uint16_t& x); -NBT_EXPORT void read_little(std::istream& is, uint32_t& x); -NBT_EXPORT void read_little(std::istream& is, uint64_t& x); -NBT_EXPORT void read_little(std::istream& is, int8_t& x); -NBT_EXPORT void read_little(std::istream& is, int16_t& x); -NBT_EXPORT void read_little(std::istream& is, int32_t& x); -NBT_EXPORT void read_little(std::istream& is, int64_t& x); -NBT_EXPORT void read_little(std::istream& is, float& x); -NBT_EXPORT void read_little(std::istream& is, double& x); + /// Reads number from stream in little endian + NBT_EXPORT void read_little(std::istream& is, uint8_t& x); + NBT_EXPORT void read_little(std::istream& is, uint16_t& x); + NBT_EXPORT void read_little(std::istream& is, uint32_t& x); + NBT_EXPORT void read_little(std::istream& is, uint64_t& x); + NBT_EXPORT void read_little(std::istream& is, int8_t& x); + NBT_EXPORT void read_little(std::istream& is, int16_t& x); + NBT_EXPORT void read_little(std::istream& is, int32_t& x); + NBT_EXPORT void read_little(std::istream& is, int64_t& x); + NBT_EXPORT void read_little(std::istream& is, float& x); + NBT_EXPORT void read_little(std::istream& is, double& x); -///Reads number from stream in big endian -NBT_EXPORT void read_big(std::istream& is, uint8_t& x); -NBT_EXPORT void read_big(std::istream& is, uint16_t& x); -NBT_EXPORT void read_big(std::istream& is, uint32_t& x); -NBT_EXPORT void read_big(std::istream& is, uint64_t& x); -NBT_EXPORT void read_big(std::istream& is, int8_t& x); -NBT_EXPORT void read_big(std::istream& is, int16_t& x); -NBT_EXPORT void read_big(std::istream& is, int32_t& x); -NBT_EXPORT void read_big(std::istream& is, int64_t& x); -NBT_EXPORT void read_big(std::istream& is, float& x); -NBT_EXPORT void read_big(std::istream& is, double& x); + /// Reads number from stream in big endian + NBT_EXPORT void read_big(std::istream& is, uint8_t& x); + NBT_EXPORT void read_big(std::istream& is, uint16_t& x); + NBT_EXPORT void read_big(std::istream& is, uint32_t& x); + NBT_EXPORT void read_big(std::istream& is, uint64_t& x); + NBT_EXPORT void read_big(std::istream& is, int8_t& x); + NBT_EXPORT void read_big(std::istream& is, int16_t& x); + NBT_EXPORT void read_big(std::istream& is, int32_t& x); + NBT_EXPORT void read_big(std::istream& is, int64_t& x); + NBT_EXPORT void read_big(std::istream& is, float& x); + NBT_EXPORT void read_big(std::istream& is, double& x); -///Writes number to stream in specified endian -template -void write(std::ostream& os, T x, endian e); + /// Writes number to stream in specified endian + template void write(std::ostream& os, T x, endian e); -///Writes number to stream in little endian -NBT_EXPORT void write_little(std::ostream& os, uint8_t x); -NBT_EXPORT void write_little(std::ostream& os, uint16_t x); -NBT_EXPORT void write_little(std::ostream& os, uint32_t x); -NBT_EXPORT void write_little(std::ostream& os, uint64_t x); -NBT_EXPORT void write_little(std::ostream& os, int8_t x); -NBT_EXPORT void write_little(std::ostream& os, int16_t x); -NBT_EXPORT void write_little(std::ostream& os, int32_t x); -NBT_EXPORT void write_little(std::ostream& os, int64_t x); -NBT_EXPORT void write_little(std::ostream& os, float x); -NBT_EXPORT void write_little(std::ostream& os, double x); + /// Writes number to stream in little endian + NBT_EXPORT void write_little(std::ostream& os, uint8_t x); + NBT_EXPORT void write_little(std::ostream& os, uint16_t x); + NBT_EXPORT void write_little(std::ostream& os, uint32_t x); + NBT_EXPORT void write_little(std::ostream& os, uint64_t x); + NBT_EXPORT void write_little(std::ostream& os, int8_t x); + NBT_EXPORT void write_little(std::ostream& os, int16_t x); + NBT_EXPORT void write_little(std::ostream& os, int32_t x); + NBT_EXPORT void write_little(std::ostream& os, int64_t x); + NBT_EXPORT void write_little(std::ostream& os, float x); + NBT_EXPORT void write_little(std::ostream& os, double x); -///Writes number to stream in big endian -NBT_EXPORT void write_big(std::ostream& os, uint8_t x); -NBT_EXPORT void write_big(std::ostream& os, uint16_t x); -NBT_EXPORT void write_big(std::ostream& os, uint32_t x); -NBT_EXPORT void write_big(std::ostream& os, uint64_t x); -NBT_EXPORT void write_big(std::ostream& os, int8_t x); -NBT_EXPORT void write_big(std::ostream& os, int16_t x); -NBT_EXPORT void write_big(std::ostream& os, int32_t x); -NBT_EXPORT void write_big(std::ostream& os, int64_t x); -NBT_EXPORT void write_big(std::ostream& os, float x); -NBT_EXPORT void write_big(std::ostream& os, double x); + /// Writes number to stream in big endian + NBT_EXPORT void write_big(std::ostream& os, uint8_t x); + NBT_EXPORT void write_big(std::ostream& os, uint16_t x); + NBT_EXPORT void write_big(std::ostream& os, uint32_t x); + NBT_EXPORT void write_big(std::ostream& os, uint64_t x); + NBT_EXPORT void write_big(std::ostream& os, int8_t x); + NBT_EXPORT void write_big(std::ostream& os, int16_t x); + NBT_EXPORT void write_big(std::ostream& os, int32_t x); + NBT_EXPORT void write_big(std::ostream& os, int64_t x); + NBT_EXPORT void write_big(std::ostream& os, float x); + NBT_EXPORT void write_big(std::ostream& os, double x); -template -void read(std::istream& is, T& x, endian e) -{ - if(e == little) - read_little(is, x); - else - read_big(is, x); -} + template void read(std::istream& is, T& x, endian e) + { + if (e == little) + read_little(is, x); + else + read_big(is, x); + } -template -void write(std::ostream& os, T x, endian e) -{ - if(e == little) - write_little(os, x); - else - write_big(os, x); -} + template void write(std::ostream& os, T x, endian e) + { + if (e == little) + write_little(os, x); + else + write_big(os, x); + } -} +} // namespace endian #endif // ENDIAN_STR_H_INCLUDED diff --git a/include/io/izlibstream.h b/include/io/izlibstream.h index f7d180f4e8..c2a8e59b04 100644 --- a/include/io/izlibstream.h +++ b/include/io/izlibstream.h @@ -33,67 +33,75 @@ namespace zlib { -/** - * @brief Stream buffer used by zlib::izlibstream - * @sa izlibstream - */ -class NBT_EXPORT inflate_streambuf : public zlib_streambuf -{ -public: - /** - * @param input the istream to wrap - * @param bufsize the size of the internal buffers - * @param window_bits the base two logarithm of the maximum window size that - * zlib will use. - * This parameter also determines which type of input to expect. - * The default argument will autodetect between zlib and gzip data. - * Refer to the zlib documentation of inflateInit2 for more details. - * - * @throw zlib_error if zlib encounters a problem during initialization - */ - explicit inflate_streambuf(std::istream& input, size_t bufsize = 32768, int window_bits = 32 + 15); - ~inflate_streambuf() noexcept; + /** + * @brief Stream buffer used by zlib::izlibstream + * @sa izlibstream + */ + class NBT_EXPORT inflate_streambuf : public zlib_streambuf + { + public: + /** + * @param input the istream to wrap + * @param bufsize the size of the internal buffers + * @param window_bits the base two logarithm of the maximum window size + * that zlib will use. This parameter also determines which type of + * input to expect. The default argument will autodetect between zlib + * and gzip data. Refer to the zlib documentation of inflateInit2 for + * more details. + * + * @throw zlib_error if zlib encounters a problem during initialization + */ + explicit inflate_streambuf(std::istream& input, size_t bufsize = 32768, + int window_bits = 32 + 15); + ~inflate_streambuf() noexcept; - ///@return the wrapped istream - std::istream& get_istr() const { return is; } + ///@return the wrapped istream + std::istream& get_istr() const + { + return is; + } -private: - std::istream& is; - bool stream_end; + private: + std::istream& is; + bool stream_end; - int_type underflow() override; -}; + int_type underflow() override; + }; -/** - * @brief An istream adapter that decompresses data using zlib - * - * This istream wraps another istream. The izlibstream will read compressed - * data from the wrapped istream and inflate (decompress) it with zlib. - * - * @note If you want to read more data from the wrapped istream after the end - * of the compressed data, then it must allow seeking. It is unavoidable for - * the izlibstream to consume more data after the compressed data. - * It will automatically attempt to seek the wrapped istream back to the point - * after the end of the compressed data. - * @sa inflate_streambuf - */ -class NBT_EXPORT izlibstream : public std::istream -{ -public: - /** - * @param input the istream to wrap - * @param bufsize the size of the internal buffers - */ - explicit izlibstream(std::istream& input, size_t bufsize = 32768): - std::istream(&buf), buf(input, bufsize) - {} - ///@return the wrapped istream - std::istream& get_istr() const { return buf.get_istr(); } + /** + * @brief An istream adapter that decompresses data using zlib + * + * This istream wraps another istream. The izlibstream will read compressed + * data from the wrapped istream and inflate (decompress) it with zlib. + * + * @note If you want to read more data from the wrapped istream after the + * end of the compressed data, then it must allow seeking. It is unavoidable + * for the izlibstream to consume more data after the compressed data. It + * will automatically attempt to seek the wrapped istream back to the point + * after the end of the compressed data. + * @sa inflate_streambuf + */ + class NBT_EXPORT izlibstream : public std::istream + { + public: + /** + * @param input the istream to wrap + * @param bufsize the size of the internal buffers + */ + explicit izlibstream(std::istream& input, size_t bufsize = 32768) + : std::istream(&buf), buf(input, bufsize) + { + } + ///@return the wrapped istream + std::istream& get_istr() const + { + return buf.get_istr(); + } -private: - inflate_streambuf buf; -}; + private: + inflate_streambuf buf; + }; -} +} // namespace zlib #endif // IZLIBSTREAM_H_INCLUDED diff --git a/include/io/ozlibstream.h b/include/io/ozlibstream.h index 5ac3dba97f..f04f33deef 100644 --- a/include/io/ozlibstream.h +++ b/include/io/ozlibstream.h @@ -33,69 +33,88 @@ namespace zlib { -/** - * @brief Stream buffer used by zlib::ozlibstream - * @sa ozlibstream - */ -class NBT_EXPORT deflate_streambuf : public zlib_streambuf -{ -public: - /** - * @param output the ostream to wrap - * @param bufsize the size of the internal buffers - * @param level the compression level, ranges from 0 to 9, or -1 for default - * - * Refer to the zlib documentation of deflateInit2 for details about the arguments. - * - * @throw zlib_error if zlib encounters a problem during initialization - */ - explicit deflate_streambuf(std::ostream& output, size_t bufsize = 32768, int level = Z_DEFAULT_COMPRESSION, int window_bits = 15, int mem_level = 8, int strategy = Z_DEFAULT_STRATEGY); - ~deflate_streambuf() noexcept; + /** + * @brief Stream buffer used by zlib::ozlibstream + * @sa ozlibstream + */ + class NBT_EXPORT deflate_streambuf : public zlib_streambuf + { + public: + /** + * @param output the ostream to wrap + * @param bufsize the size of the internal buffers + * @param level the compression level, ranges from 0 to 9, or -1 for + * default + * + * Refer to the zlib documentation of deflateInit2 for details about the + * arguments. + * + * @throw zlib_error if zlib encounters a problem during initialization + */ + explicit deflate_streambuf(std::ostream& output, size_t bufsize = 32768, + int level = Z_DEFAULT_COMPRESSION, + int window_bits = 15, int mem_level = 8, + int strategy = Z_DEFAULT_STRATEGY); + ~deflate_streambuf() noexcept; - ///@return the wrapped ostream - std::ostream& get_ostr() const { return os; } + ///@return the wrapped ostream + std::ostream& get_ostr() const + { + return os; + } - ///Finishes compression and writes all pending data to the output - void close(); -private: - std::ostream& os; + /// Finishes compression and writes all pending data to the output + void close(); - void deflate_chunk(int flush = Z_NO_FLUSH); + private: + std::ostream& os; - int_type overflow(int_type ch) override; - int sync() override; -}; + void deflate_chunk(int flush = Z_NO_FLUSH); -/** - * @brief An ostream adapter that compresses data using zlib - * - * This ostream wraps another ostream. Data written to an ozlibstream will be - * deflated (compressed) with zlib and written to the wrapped ostream. - * - * @sa deflate_streambuf - */ -class NBT_EXPORT ozlibstream : public std::ostream -{ -public: - /** - * @param output the ostream to wrap - * @param level the compression level, ranges from 0 to 9, or -1 for default - * @param gzip if true, the output will be in gzip format rather than zlib - * @param bufsize the size of the internal buffers - */ - explicit ozlibstream(std::ostream& output, int level = Z_DEFAULT_COMPRESSION, bool gzip = false, size_t bufsize = 32768): - std::ostream(&buf), buf(output, bufsize, level, 15 + (gzip ? 16 : 0)) - {} + int_type overflow(int_type ch) override; + int sync() override; + }; + + /** + * @brief An ostream adapter that compresses data using zlib + * + * This ostream wraps another ostream. Data written to an ozlibstream will + * be deflated (compressed) with zlib and written to the wrapped ostream. + * + * @sa deflate_streambuf + */ + class NBT_EXPORT ozlibstream : public std::ostream + { + public: + /** + * @param output the ostream to wrap + * @param level the compression level, ranges from 0 to 9, or -1 for + * default + * @param gzip if true, the output will be in gzip format rather than + * zlib + * @param bufsize the size of the internal buffers + */ + explicit ozlibstream(std::ostream& output, + int level = Z_DEFAULT_COMPRESSION, + bool gzip = false, size_t bufsize = 32768) + : std::ostream(&buf), + buf(output, bufsize, level, 15 + (gzip ? 16 : 0)) + { + } + + ///@return the wrapped ostream + std::ostream& get_ostr() const + { + return buf.get_ostr(); + } - ///@return the wrapped ostream - std::ostream& get_ostr() const { return buf.get_ostr(); } + /// Finishes compression and writes all pending data to the output + void close(); - ///Finishes compression and writes all pending data to the output - void close(); -private: - deflate_streambuf buf; -}; + private: + deflate_streambuf buf; + }; -} +} // namespace zlib #endif // OZLIBSTREAM_H_INCLUDED diff --git a/include/io/stream_reader.h b/include/io/stream_reader.h index 821a9ff8d3..05f8d99c92 100644 --- a/include/io/stream_reader.h +++ b/include/io/stream_reader.h @@ -36,108 +36,115 @@ namespace nbt { -namespace io -{ - -///Exception that gets thrown when reading is not successful -class NBT_EXPORT input_error : public std::runtime_error -{ - using std::runtime_error::runtime_error; -}; - -/** - * @brief Reads a named tag from the stream, making sure that it is a compound - * @param is the stream to read from - * @param e the byte order of the source data. The Java edition - * of Minecraft uses Big Endian, the Pocket edition uses Little Endian - * @throw input_error on failure, or if the tag in the stream is not a compound - */ -NBT_EXPORT std::pair> read_compound(std::istream& is, endian::endian e = endian::big); - -/** - * @brief Reads a named tag from the stream - * @param is the stream to read from - * @param e the byte order of the source data. The Java edition - * of Minecraft uses Big Endian, the Pocket edition uses Little Endian - * @throw input_error on failure - */ -NBT_EXPORT std::pair> read_tag(std::istream& is, endian::endian e = endian::big); - -/** - * @brief Helper class for reading NBT tags from input streams - * - * Can be reused to read multiple tags - */ -class NBT_EXPORT stream_reader -{ -public: - /** - * @param is the stream to read from - * @param e the byte order of the source data. The Java edition - * of Minecraft uses Big Endian, the Pocket edition uses Little Endian - */ - explicit stream_reader(std::istream& is, endian::endian e = endian::big) noexcept; - - ///Returns the stream - std::istream& get_istr() const; - ///Returns the byte order - endian::endian get_endian() const; - - /** - * @brief Reads a named tag from the stream, making sure that it is a compound - * @throw input_error on failure, or if the tag in the stream is not a compound - */ - std::pair> read_compound(); - - /** - * @brief Reads a named tag from the stream - * @throw input_error on failure - */ - std::pair> read_tag(); - - /** - * @brief Reads a tag of the given type without name from the stream - * @throw input_error on failure - */ - std::unique_ptr read_payload(tag_type type); - - /** - * @brief Reads a tag type from the stream - * @param allow_end whether to consider tag_type::End valid - * @throw input_error on failure - */ - tag_type read_type(bool allow_end = false); - - /** - * @brief Reads a binary number from the stream - * - * On failure, will set the failbit on the stream. - */ - template - void read_num(T& x); - - /** - * @brief Reads an NBT string from the stream - * - * An NBT string consists of two bytes indicating the length, followed by - * the characters encoded in modified UTF-8. - * @throw input_error on failure - */ - std::string read_string(); - -private: - std::istream& is; - int depth = 0; - const endian::endian endian; -}; - -template -void stream_reader::read_num(T& x) -{ - endian::read(is, x, endian); -} - -} -} + namespace io + { + + /// Exception that gets thrown when reading is not successful + class NBT_EXPORT input_error : public std::runtime_error + { + using std::runtime_error::runtime_error; + }; + + /** + * @brief Reads a named tag from the stream, making sure that it is a + * compound + * @param is the stream to read from + * @param e the byte order of the source data. The Java edition + * of Minecraft uses Big Endian, the Pocket edition uses Little Endian + * @throw input_error on failure, or if the tag in the stream is not a + * compound + */ + NBT_EXPORT std::pair> + read_compound(std::istream& is, endian::endian e = endian::big); + + /** + * @brief Reads a named tag from the stream + * @param is the stream to read from + * @param e the byte order of the source data. The Java edition + * of Minecraft uses Big Endian, the Pocket edition uses Little Endian + * @throw input_error on failure + */ + NBT_EXPORT std::pair> + read_tag(std::istream& is, endian::endian e = endian::big); + + /** + * @brief Helper class for reading NBT tags from input streams + * + * Can be reused to read multiple tags + */ + class NBT_EXPORT stream_reader + { + public: + /** + * @param is the stream to read from + * @param e the byte order of the source data. The Java edition + * of Minecraft uses Big Endian, the Pocket edition uses Little + * Endian + */ + explicit stream_reader(std::istream& is, + endian::endian e = endian::big) noexcept; + + /// Returns the stream + std::istream& get_istr() const; + /// Returns the byte order + endian::endian get_endian() const; + + /** + * @brief Reads a named tag from the stream, making sure that it is + * a compound + * @throw input_error on failure, or if the tag in the stream is not + * a compound + */ + std::pair> + read_compound(); + + /** + * @brief Reads a named tag from the stream + * @throw input_error on failure + */ + std::pair> read_tag(); + + /** + * @brief Reads a tag of the given type without name from the stream + * @throw input_error on failure + */ + std::unique_ptr read_payload(tag_type type); + + /** + * @brief Reads a tag type from the stream + * @param allow_end whether to consider tag_type::End valid + * @throw input_error on failure + */ + tag_type read_type(bool allow_end = false); + + /** + * @brief Reads a binary number from the stream + * + * On failure, will set the failbit on the stream. + */ + template void read_num(T& x); + + /** + * @brief Reads an NBT string from the stream + * + * An NBT string consists of two bytes indicating the length, + * followed by the characters encoded in modified UTF-8. + * @throw input_error on failure + */ + std::string read_string(); + + private: + std::istream& is; + int depth = 0; + const endian::endian endian; + }; + + template void stream_reader::read_num(T& x) + { + endian::read(is, x, endian); + } + + } // namespace io +} // namespace nbt #endif // STREAM_READER_H_INCLUDED diff --git a/include/io/stream_writer.h b/include/io/stream_writer.h index 32a01c89c9..04f039948c 100644 --- a/include/io/stream_writer.h +++ b/include/io/stream_writer.h @@ -33,95 +33,110 @@ namespace nbt { -namespace io -{ - -/* Not sure if that is even needed -///Exception that gets thrown when writing is not successful -class output_error : public std::runtime_error -{ - using std::runtime_error::runtime_error; -};*/ - -/** - * @brief Writes a named tag into the stream, including the tag type - * @param key the name of the tag - * @param t the tag - * @param os the stream to write to - * @param e the byte order of the written data. The Java edition - * of Minecraft uses Big Endian, the Pocket edition uses Little Endian - */ -NBT_EXPORT void write_tag(const std::string& key, const tag& t, std::ostream& os, endian::endian e = endian::big); - -/** - * @brief Helper class for writing NBT tags to output streams - * - * Can be reused to write multiple tags - */ -class NBT_EXPORT stream_writer -{ -public: - ///Maximum length of an NBT string (16 bit unsigned) - static constexpr size_t max_string_len = UINT16_MAX; - ///Maximum length of an NBT list or array (32 bit signed) - static constexpr uint32_t max_array_len = INT32_MAX; - - /** - * @param os the stream to write to - * @param e the byte order of the written data. The Java edition - * of Minecraft uses Big Endian, the Pocket edition uses Little Endian - */ - explicit stream_writer(std::ostream& os, endian::endian e = endian::big) noexcept: - os(os), endian(e) - {} - - ///Returns the stream - std::ostream& get_ostr() const { return os; } - ///Returns the byte order - endian::endian get_endian() const { return endian; } - - /** - * @brief Writes a named tag into the stream, including the tag type - */ - void write_tag(const std::string& key, const tag& t); - - /** - * @brief Writes the given tag's payload into the stream - */ - void write_payload(const tag& t) { t.write_payload(*this); } - - /** - * @brief Writes a tag type to the stream - */ - void write_type(tag_type tt) { write_num(static_cast(tt)); } - - /** - * @brief Writes a binary number to the stream - */ - template - void write_num(T x); - - /** - * @brief Writes an NBT string to the stream - * - * An NBT string consists of two bytes indicating the length, followed by - * the characters encoded in modified UTF-8. - * @throw std::length_error if the string is too long for NBT - */ - void write_string(const std::string& str); - -private: - std::ostream& os; - const endian::endian endian; -}; - -template -void stream_writer::write_num(T x) -{ - endian::write(os, x, endian); -} - -} -} + namespace io + { + + /* Not sure if that is even needed + ///Exception that gets thrown when writing is not successful + class output_error : public std::runtime_error + { + using std::runtime_error::runtime_error; + };*/ + + /** + * @brief Writes a named tag into the stream, including the tag type + * @param key the name of the tag + * @param t the tag + * @param os the stream to write to + * @param e the byte order of the written data. The Java edition + * of Minecraft uses Big Endian, the Pocket edition uses Little Endian + */ + NBT_EXPORT void write_tag(const std::string& key, const tag& t, + std::ostream& os, + endian::endian e = endian::big); + + /** + * @brief Helper class for writing NBT tags to output streams + * + * Can be reused to write multiple tags + */ + class NBT_EXPORT stream_writer + { + public: + /// Maximum length of an NBT string (16 bit unsigned) + static constexpr size_t max_string_len = UINT16_MAX; + /// Maximum length of an NBT list or array (32 bit signed) + static constexpr uint32_t max_array_len = INT32_MAX; + + /** + * @param os the stream to write to + * @param e the byte order of the written data. The Java edition + * of Minecraft uses Big Endian, the Pocket edition uses Little + * Endian + */ + explicit stream_writer(std::ostream& os, + endian::endian e = endian::big) noexcept + : os(os), endian(e) + { + } + + /// Returns the stream + std::ostream& get_ostr() const + { + return os; + } + /// Returns the byte order + endian::endian get_endian() const + { + return endian; + } + + /** + * @brief Writes a named tag into the stream, including the tag type + */ + void write_tag(const std::string& key, const tag& t); + + /** + * @brief Writes the given tag's payload into the stream + */ + void write_payload(const tag& t) + { + t.write_payload(*this); + } + + /** + * @brief Writes a tag type to the stream + */ + void write_type(tag_type tt) + { + write_num(static_cast(tt)); + } + + /** + * @brief Writes a binary number to the stream + */ + template void write_num(T x); + + /** + * @brief Writes an NBT string to the stream + * + * An NBT string consists of two bytes indicating the length, + * followed by the characters encoded in modified UTF-8. + * @throw std::length_error if the string is too long for NBT + */ + void write_string(const std::string& str); + + private: + std::ostream& os; + const endian::endian endian; + }; + + template void stream_writer::write_num(T x) + { + endian::write(os, x, endian); + } + + } // namespace io +} // namespace nbt #endif // STREAM_WRITER_H_INCLUDED diff --git a/include/io/zlib_streambuf.h b/include/io/zlib_streambuf.h index e8eea1822f..91a660b9f2 100644 --- a/include/io/zlib_streambuf.h +++ b/include/io/zlib_streambuf.h @@ -16,37 +16,36 @@ namespace zlib { -///Exception thrown in case zlib encounters a problem -class NBT_EXPORT zlib_error : public std::runtime_error -{ -public: - const int errcode; - - zlib_error(const char* msg, int errcode): - std::runtime_error(msg - ? std::string(zError(errcode)) + ": " + msg - : zError(errcode)), - errcode(errcode) - {} -}; - -///Base class for deflate_streambuf and inflate_streambuf -class zlib_streambuf : public std::streambuf -{ -protected: - std::vector in; - std::vector out; - z_stream zstr; - - explicit zlib_streambuf(size_t bufsize): - in(bufsize), out(bufsize) - { - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - zstr.opaque = Z_NULL; - } -}; - -} + /// Exception thrown in case zlib encounters a problem + class NBT_EXPORT zlib_error : public std::runtime_error + { + public: + const int errcode; + + zlib_error(const char* msg, int errcode) + : std::runtime_error(msg ? std::string(zError(errcode)) + ": " + msg + : zError(errcode)), + errcode(errcode) + { + } + }; + + /// Base class for deflate_streambuf and inflate_streambuf + class zlib_streambuf : public std::streambuf + { + protected: + std::vector in; + std::vector out; + z_stream zstr; + + explicit zlib_streambuf(size_t bufsize) : in(bufsize), out(bufsize) + { + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = Z_NULL; + } + }; + +} // namespace zlib #endif // ZLIB_STREAMBUF_H_INCLUDED diff --git a/include/make_unique.h b/include/make_unique.h index a475a4a734..513cbcf69f 100644 --- a/include/make_unique.h +++ b/include/make_unique.h @@ -31,13 +31,13 @@ namespace nbt { -///Creates a new object of type T and returns a std::unique_ptr to it -template -std::unique_ptr make_unique(Args&&... args) -{ - return std::unique_ptr(new T(std::forward(args)...)); -} + /// Creates a new object of type T and returns a std::unique_ptr to it + template + std::unique_ptr make_unique(Args&&... args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } -} +} // namespace nbt #endif // MAKE_UNIQUE_H_INCLUDED diff --git a/include/nbt_visitor.h b/include/nbt_visitor.h index 0b37161a5f..e244162f83 100644 --- a/include/nbt_visitor.h +++ b/include/nbt_visitor.h @@ -31,58 +31,58 @@ namespace nbt { -/** - * @brief Base class for visitors of tags - * - * Implementing the Visitor pattern - */ -class nbt_visitor -{ -public: - virtual ~nbt_visitor() noexcept = 0; //Abstract class + /** + * @brief Base class for visitors of tags + * + * Implementing the Visitor pattern + */ + class nbt_visitor + { + public: + virtual ~nbt_visitor() noexcept = 0; // Abstract class - virtual void visit(tag_byte&) {} - virtual void visit(tag_short&) {} - virtual void visit(tag_int&) {} - virtual void visit(tag_long&) {} - virtual void visit(tag_float&) {} - virtual void visit(tag_double&) {} - virtual void visit(tag_byte_array&) {} - virtual void visit(tag_string&) {} - virtual void visit(tag_list&) {} - virtual void visit(tag_compound&) {} - virtual void visit(tag_int_array&) {} - virtual void visit(tag_long_array&) {} -}; + virtual void visit(tag_byte&) {} + virtual void visit(tag_short&) {} + virtual void visit(tag_int&) {} + virtual void visit(tag_long&) {} + virtual void visit(tag_float&) {} + virtual void visit(tag_double&) {} + virtual void visit(tag_byte_array&) {} + virtual void visit(tag_string&) {} + virtual void visit(tag_list&) {} + virtual void visit(tag_compound&) {} + virtual void visit(tag_int_array&) {} + virtual void visit(tag_long_array&) {} + }; -/** - * @brief Base class for visitors of constant tags - * - * Implementing the Visitor pattern - */ -class const_nbt_visitor -{ -public: - virtual ~const_nbt_visitor() noexcept = 0; //Abstract class + /** + * @brief Base class for visitors of constant tags + * + * Implementing the Visitor pattern + */ + class const_nbt_visitor + { + public: + virtual ~const_nbt_visitor() noexcept = 0; // Abstract class - virtual void visit(const tag_byte&) {} - virtual void visit(const tag_short&) {} - virtual void visit(const tag_int&) {} - virtual void visit(const tag_long&) {} - virtual void visit(const tag_float&) {} - virtual void visit(const tag_double&) {} - virtual void visit(const tag_byte_array&) {} - virtual void visit(const tag_string&) {} - virtual void visit(const tag_list&) {} - virtual void visit(const tag_compound&) {} - virtual void visit(const tag_int_array&) {} - virtual void visit(const tag_long_array&) {} -}; + virtual void visit(const tag_byte&) {} + virtual void visit(const tag_short&) {} + virtual void visit(const tag_int&) {} + virtual void visit(const tag_long&) {} + virtual void visit(const tag_float&) {} + virtual void visit(const tag_double&) {} + virtual void visit(const tag_byte_array&) {} + virtual void visit(const tag_string&) {} + virtual void visit(const tag_list&) {} + virtual void visit(const tag_compound&) {} + virtual void visit(const tag_int_array&) {} + virtual void visit(const tag_long_array&) {} + }; -inline nbt_visitor::~nbt_visitor() noexcept {} + inline nbt_visitor::~nbt_visitor() noexcept {} -inline const_nbt_visitor::~const_nbt_visitor() noexcept {} + inline const_nbt_visitor::~const_nbt_visitor() noexcept {} -} +} // namespace nbt #endif // NBT_VISITOR_H_INCLUDED diff --git a/include/primitive_detail.h b/include/primitive_detail.h index de070d3320..c55cd18398 100644 --- a/include/primitive_detail.h +++ b/include/primitive_detail.h @@ -32,21 +32,43 @@ namespace nbt { -namespace detail -{ - ///Meta-struct that holds the tag_type value for a specific primitive type - template struct get_primitive_type - { static_assert(sizeof(T) != sizeof(T), "Invalid type paramter for tag_primitive, can only use types that NBT uses"); }; + namespace detail + { + /// Meta-struct that holds the tag_type value for a specific primitive + /// type + template struct get_primitive_type { + static_assert(sizeof(T) != sizeof(T), + "Invalid type paramter for tag_primitive, can only " + "use types that NBT uses"); + }; - template<> struct get_primitive_type : public std::integral_constant {}; - template<> struct get_primitive_type : public std::integral_constant {}; - template<> struct get_primitive_type : public std::integral_constant {}; - template<> struct get_primitive_type : public std::integral_constant {}; - template<> struct get_primitive_type : public std::integral_constant {}; - template<> struct get_primitive_type : public std::integral_constant {}; -} + template <> + struct get_primitive_type + : public std::integral_constant { + }; + template <> + struct get_primitive_type + : public std::integral_constant { + }; + template <> + struct get_primitive_type + : public std::integral_constant { + }; + template <> + struct get_primitive_type + : public std::integral_constant { + }; + template <> + struct get_primitive_type + : public std::integral_constant { + }; + template <> + struct get_primitive_type + : public std::integral_constant { + }; + } // namespace detail -} +} // namespace nbt ///@endcond #endif // PRIMITIVE_DETAIL_H_INCLUDED diff --git a/include/tag.h b/include/tag.h index ac3b49fa3a..92432cf55a 100644 --- a/include/tag.h +++ b/include/tag.h @@ -34,142 +34,142 @@ namespace nbt { -///Tag type values used in the binary format -enum class tag_type : int8_t -{ - End = 0, - Byte = 1, - Short = 2, - Int = 3, - Long = 4, - Float = 5, - Double = 6, - Byte_Array = 7, - String = 8, - List = 9, - Compound = 10, - Int_Array = 11, - Long_Array = 12, - Null = -1 ///< Used to denote empty @ref value s -}; - -/** - * @brief Returns whether the given number falls within the range of valid tag types - * @param allow_end whether to consider tag_type::End (0) valid - */ -NBT_EXPORT bool is_valid_type(int type, bool allow_end = false); - -//Forward declarations -class nbt_visitor; -class const_nbt_visitor; -namespace io -{ - class stream_reader; - class stream_writer; -} - -///Base class for all NBT tag classes -class NBT_EXPORT tag -{ -public: - //Virtual destructor - virtual ~tag() noexcept {} - - ///Returns the type of the tag - virtual tag_type get_type() const noexcept = 0; - - //Polymorphic clone methods - virtual std::unique_ptr clone() const& = 0; - virtual std::unique_ptr move_clone() && = 0; - std::unique_ptr clone() &&; - - /** - * @brief Returns a reference to the tag as an instance of T - * @throw std::bad_cast if the tag is not of type T - */ - template - T& as(); - template - const T& as() const; - - /** - * @brief Move-assigns the given tag if the class is the same - * @throw std::bad_cast if @c rhs is not the same type as @c *this - */ - virtual tag& assign(tag&& rhs) = 0; - - /** - * @brief Calls the appropriate overload of @c visit() on the visitor with - * @c *this as argument - * - * Implementing the Visitor pattern - */ - virtual void accept(nbt_visitor& visitor) = 0; - virtual void accept(const_nbt_visitor& visitor) const = 0; - - /** - * @brief Reads the tag's payload from the stream - * @throw io::stream_reader::input_error on failure - */ - virtual void read_payload(io::stream_reader& reader) = 0; - - /** - * @brief Writes the tag's payload into the stream - */ - virtual void write_payload(io::stream_writer& writer) const = 0; - - /** - * @brief Default-constructs a new tag of the given type - * @throw std::invalid_argument if the type is not valid (e.g. End or Null) - */ - static std::unique_ptr create(tag_type type); - /** - * @brief Constructs a numeric tag of the given type and value - * @throw std::invalid_argument if the type is not numeric - */ - static std::unique_ptr create(tag_type type, int8_t val); - static std::unique_ptr create(tag_type type, int16_t val); - static std::unique_ptr create(tag_type type, int32_t val); - static std::unique_ptr create(tag_type type, int64_t val); - static std::unique_ptr create(tag_type type, float val); - static std::unique_ptr create(tag_type type, double val); - - friend NBT_EXPORT bool operator==(const tag& lhs, const tag& rhs); - friend NBT_EXPORT bool operator!=(const tag& lhs, const tag& rhs); - -private: - /** - * @brief Checks for equality to a tag of the same type - * @param rhs an instance of the same class as @c *this - */ - virtual bool equals(const tag& rhs) const = 0; -}; - -///Output operator for tag types -NBT_EXPORT std::ostream& operator<<(std::ostream& os, tag_type tt); - -/** - * @brief Output operator for tags - * - * Uses @ref text::json_formatter - * @relates tag - */ -NBT_EXPORT std::ostream& operator<<(std::ostream& os, const tag& t); - -template -T& tag::as() -{ - static_assert(std::is_base_of::value, "T must be a subclass of tag"); - return dynamic_cast(*this); -} - -template -const T& tag::as() const -{ - static_assert(std::is_base_of::value, "T must be a subclass of tag"); - return dynamic_cast(*this); -} - -} + /// Tag type values used in the binary format + enum class tag_type : int8_t { + End = 0, + Byte = 1, + Short = 2, + Int = 3, + Long = 4, + Float = 5, + Double = 6, + Byte_Array = 7, + String = 8, + List = 9, + Compound = 10, + Int_Array = 11, + Long_Array = 12, + Null = -1 ///< Used to denote empty @ref value s + }; + + /** + * @brief Returns whether the given number falls within the range of valid + * tag types + * @param allow_end whether to consider tag_type::End (0) valid + */ + NBT_EXPORT bool is_valid_type(int type, bool allow_end = false); + + // Forward declarations + class nbt_visitor; + class const_nbt_visitor; + namespace io + { + class stream_reader; + class stream_writer; + } // namespace io + + /// Base class for all NBT tag classes + class NBT_EXPORT tag + { + public: + // Virtual destructor + virtual ~tag() noexcept {} + + /// Returns the type of the tag + virtual tag_type get_type() const noexcept = 0; + + // Polymorphic clone methods + virtual std::unique_ptr clone() const& = 0; + virtual std::unique_ptr move_clone() && = 0; + std::unique_ptr clone() &&; + + /** + * @brief Returns a reference to the tag as an instance of T + * @throw std::bad_cast if the tag is not of type T + */ + template T& as(); + template const T& as() const; + + /** + * @brief Move-assigns the given tag if the class is the same + * @throw std::bad_cast if @c rhs is not the same type as @c *this + */ + virtual tag& assign(tag&& rhs) = 0; + + /** + * @brief Calls the appropriate overload of @c visit() on the visitor + * with + * @c *this as argument + * + * Implementing the Visitor pattern + */ + virtual void accept(nbt_visitor& visitor) = 0; + virtual void accept(const_nbt_visitor& visitor) const = 0; + + /** + * @brief Reads the tag's payload from the stream + * @throw io::stream_reader::input_error on failure + */ + virtual void read_payload(io::stream_reader& reader) = 0; + + /** + * @brief Writes the tag's payload into the stream + */ + virtual void write_payload(io::stream_writer& writer) const = 0; + + /** + * @brief Default-constructs a new tag of the given type + * @throw std::invalid_argument if the type is not valid (e.g. End or + * Null) + */ + static std::unique_ptr create(tag_type type); + /** + * @brief Constructs a numeric tag of the given type and value + * @throw std::invalid_argument if the type is not numeric + */ + static std::unique_ptr create(tag_type type, int8_t val); + static std::unique_ptr create(tag_type type, int16_t val); + static std::unique_ptr create(tag_type type, int32_t val); + static std::unique_ptr create(tag_type type, int64_t val); + static std::unique_ptr create(tag_type type, float val); + static std::unique_ptr create(tag_type type, double val); + + friend NBT_EXPORT bool operator==(const tag& lhs, const tag& rhs); + friend NBT_EXPORT bool operator!=(const tag& lhs, const tag& rhs); + + private: + /** + * @brief Checks for equality to a tag of the same type + * @param rhs an instance of the same class as @c *this + */ + virtual bool equals(const tag& rhs) const = 0; + }; + + /// Output operator for tag types + NBT_EXPORT std::ostream& operator<<(std::ostream& os, tag_type tt); + + /** + * @brief Output operator for tags + * + * Uses @ref text::json_formatter + * @relates tag + */ + NBT_EXPORT std::ostream& operator<<(std::ostream& os, const tag& t); + + template T& tag::as() + { + static_assert(std::is_base_of::value, + "T must be a subclass of tag"); + return dynamic_cast(*this); + } + + template const T& tag::as() const + { + static_assert(std::is_base_of::value, + "T must be a subclass of tag"); + return dynamic_cast(*this); + } + +} // namespace nbt #endif // TAG_H_INCLUDED diff --git a/include/tag_array.h b/include/tag_array.h index de1dd0953a..290680573f 100644 --- a/include/tag_array.h +++ b/include/tag_array.h @@ -36,206 +36,271 @@ namespace nbt { -///@cond -namespace detail -{ - ///Meta-struct that holds the tag_type value for a specific array type - template struct get_array_type - { static_assert(sizeof(T) != sizeof(T), "Invalid type paramter for tag_array, can only use byte or int"); }; - - template<> struct get_array_type : public std::integral_constant {}; - template<> struct get_array_type : public std::integral_constant {}; - template<> struct get_array_type : public std::integral_constant {}; -} -///@cond - -/** - * @brief Tag that contains an array of byte or int values - * - * Common class for tag_byte_array, tag_int_array and tag_long_array. - */ -template -class tag_array final : public detail::crtp_tag> -{ -public: - //Iterator types - typedef typename std::vector::iterator iterator; - typedef typename std::vector::const_iterator const_iterator; - - ///The type of the contained values - typedef T value_type; - - ///The type of the tag - static constexpr tag_type type = detail::get_array_type::value; - - ///Constructs an empty array - tag_array() {} - - ///Constructs an array with the given values - tag_array(std::initializer_list init): data(init) {} - tag_array(std::vector&& vec) noexcept: data(std::move(vec)) {} - - ///Returns a reference to the vector that contains the values - std::vector& get() { return data; } - const std::vector& get() const { return data; } - - /** - * @brief Accesses a value by index with bounds checking - * @throw std::out_of_range if the index is out of range - */ - T& at(size_t i) { return data.at(i); } - T at(size_t i) const { return data.at(i); } - - /** - * @brief Accesses a value by index - * - * No bounds checking is performed. - */ - T& operator[](size_t i) { return data[i]; } - T operator[](size_t i) const { return data[i]; } - - ///Appends a value at the end of the array - void push_back(T val) { data.push_back(val); } - - ///Removes the last element from the array - void pop_back() { data.pop_back(); } - - ///Returns the number of values in the array - size_t size() const { return data.size(); } - - ///Erases all values from the array. - void clear() { data.clear(); } - - //Iterators - iterator begin() { return data.begin(); } - iterator end() { return data.end(); } - const_iterator begin() const { return data.begin(); } - const_iterator end() const { return data.end(); } - const_iterator cbegin() const { return data.cbegin(); } - const_iterator cend() const { return data.cend(); } - - void read_payload(io::stream_reader& reader) override; - /** - * @inheritdoc - * @throw std::length_error if the array is too large for NBT - */ - void write_payload(io::stream_writer& writer) const override; - -private: - std::vector data; -}; - -template bool operator==(const tag_array& lhs, const tag_array& rhs) -{ return lhs.get() == rhs.get(); } -template bool operator!=(const tag_array& lhs, const tag_array& rhs) -{ return !(lhs == rhs); } - -//Slightly different between byte_array and int_array -//Reading -template<> -inline void tag_array::read_payload(io::stream_reader& reader) -{ - int32_t length; - reader.read_num(length); - if(length < 0) - reader.get_istr().setstate(std::ios::failbit); - if(!reader.get_istr()) - throw io::input_error("Error reading length of tag_byte_array"); - - data.resize(length); - reader.get_istr().read(reinterpret_cast(data.data()), length); - if(!reader.get_istr()) - throw io::input_error("Error reading contents of tag_byte_array"); -} - -template -inline void tag_array::read_payload(io::stream_reader& reader) -{ - int32_t length; - reader.read_num(length); - if(length < 0) - reader.get_istr().setstate(std::ios::failbit); - if(!reader.get_istr()) - throw io::input_error("Error reading length of generic array tag"); - - data.clear(); - data.reserve(length); - for(T i = 0; i < length; ++i) - { - T val; - reader.read_num(val); - data.push_back(val); - } - if(!reader.get_istr()) - throw io::input_error("Error reading contents of generic array tag"); -} - -template<> -inline void tag_array::read_payload(io::stream_reader& reader) -{ - int32_t length; - reader.read_num(length); - if(length < 0) - reader.get_istr().setstate(std::ios::failbit); - if(!reader.get_istr()) - throw io::input_error("Error reading length of tag_long_array"); - - data.clear(); - data.reserve(length); - for(int32_t i = 0; i < length; ++i) - { - int64_t val; - reader.read_num(val); - data.push_back(val); - } - if(!reader.get_istr()) - throw io::input_error("Error reading contents of tag_long_array"); -} - -//Writing -template<> -inline void tag_array::write_payload(io::stream_writer& writer) const -{ - if(size() > io::stream_writer::max_array_len) - { - writer.get_ostr().setstate(std::ios::failbit); - throw std::length_error("Byte array is too large for NBT"); - } - writer.write_num(static_cast(size())); - writer.get_ostr().write(reinterpret_cast(data.data()), data.size()); -} - -template -inline void tag_array::write_payload(io::stream_writer& writer) const -{ - if(size() > io::stream_writer::max_array_len) - { - writer.get_ostr().setstate(std::ios::failbit); - throw std::length_error("Generic array is too large for NBT"); - } - writer.write_num(static_cast(size())); - for(T i: data) - writer.write_num(i); -} - -template<> -inline void tag_array::write_payload(io::stream_writer& writer) const -{ - if(size() > io::stream_writer::max_array_len) - { - writer.get_ostr().setstate(std::ios::failbit); - throw std::length_error("Long array is too large for NBT"); - } - writer.write_num(static_cast(size())); - for(int64_t i: data) - writer.write_num(i); -} - -//Typedefs that should be used instead of the template tag_array. -typedef tag_array tag_byte_array; -typedef tag_array tag_int_array; -typedef tag_array tag_long_array; - -} + ///@cond + namespace detail + { + /// Meta-struct that holds the tag_type value for a specific array type + template struct get_array_type { + static_assert(sizeof(T) != sizeof(T), + "Invalid type paramter for tag_array, can only use " + "byte or int"); + }; + + template <> + struct get_array_type + : public std::integral_constant { + }; + template <> + struct get_array_type + : public std::integral_constant { + }; + template <> + struct get_array_type + : public std::integral_constant { + }; + } // namespace detail + ///@cond + + /** + * @brief Tag that contains an array of byte or int values + * + * Common class for tag_byte_array, tag_int_array and tag_long_array. + */ + template + class tag_array final : public detail::crtp_tag> + { + public: + // Iterator types + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; + + /// The type of the contained values + typedef T value_type; + + /// The type of the tag + static constexpr tag_type type = detail::get_array_type::value; + + /// Constructs an empty array + tag_array() {} + + /// Constructs an array with the given values + tag_array(std::initializer_list init) : data(init) {} + tag_array(std::vector&& vec) noexcept : data(std::move(vec)) {} + + /// Returns a reference to the vector that contains the values + std::vector& get() + { + return data; + } + const std::vector& get() const + { + return data; + } + + /** + * @brief Accesses a value by index with bounds checking + * @throw std::out_of_range if the index is out of range + */ + T& at(size_t i) + { + return data.at(i); + } + T at(size_t i) const + { + return data.at(i); + } + + /** + * @brief Accesses a value by index + * + * No bounds checking is performed. + */ + T& operator[](size_t i) + { + return data[i]; + } + T operator[](size_t i) const + { + return data[i]; + } + + /// Appends a value at the end of the array + void push_back(T val) + { + data.push_back(val); + } + + /// Removes the last element from the array + void pop_back() + { + data.pop_back(); + } + + /// Returns the number of values in the array + size_t size() const + { + return data.size(); + } + + /// Erases all values from the array. + void clear() + { + data.clear(); + } + + // Iterators + iterator begin() + { + return data.begin(); + } + iterator end() + { + return data.end(); + } + const_iterator begin() const + { + return data.begin(); + } + const_iterator end() const + { + return data.end(); + } + const_iterator cbegin() const + { + return data.cbegin(); + } + const_iterator cend() const + { + return data.cend(); + } + + void read_payload(io::stream_reader& reader) override; + /** + * @inheritdoc + * @throw std::length_error if the array is too large for NBT + */ + void write_payload(io::stream_writer& writer) const override; + + private: + std::vector data; + }; + + template + bool operator==(const tag_array& lhs, const tag_array& rhs) + { + return lhs.get() == rhs.get(); + } + template + bool operator!=(const tag_array& lhs, const tag_array& rhs) + { + return !(lhs == rhs); + } + + // Slightly different between byte_array and int_array + // Reading + template <> + inline void tag_array::read_payload(io::stream_reader& reader) + { + int32_t length; + reader.read_num(length); + if (length < 0) + reader.get_istr().setstate(std::ios::failbit); + if (!reader.get_istr()) + throw io::input_error("Error reading length of tag_byte_array"); + + data.resize(length); + reader.get_istr().read(reinterpret_cast(data.data()), length); + if (!reader.get_istr()) + throw io::input_error("Error reading contents of tag_byte_array"); + } + + template + inline void tag_array::read_payload(io::stream_reader& reader) + { + int32_t length; + reader.read_num(length); + if (length < 0) + reader.get_istr().setstate(std::ios::failbit); + if (!reader.get_istr()) + throw io::input_error("Error reading length of generic array tag"); + + data.clear(); + data.reserve(length); + for (T i = 0; i < length; ++i) { + T val; + reader.read_num(val); + data.push_back(val); + } + if (!reader.get_istr()) + throw io::input_error( + "Error reading contents of generic array tag"); + } + + template <> + inline void tag_array::read_payload(io::stream_reader& reader) + { + int32_t length; + reader.read_num(length); + if (length < 0) + reader.get_istr().setstate(std::ios::failbit); + if (!reader.get_istr()) + throw io::input_error("Error reading length of tag_long_array"); + + data.clear(); + data.reserve(length); + for (int32_t i = 0; i < length; ++i) { + int64_t val; + reader.read_num(val); + data.push_back(val); + } + if (!reader.get_istr()) + throw io::input_error("Error reading contents of tag_long_array"); + } + + // Writing + template <> + inline void + tag_array::write_payload(io::stream_writer& writer) const + { + if (size() > io::stream_writer::max_array_len) { + writer.get_ostr().setstate(std::ios::failbit); + throw std::length_error("Byte array is too large for NBT"); + } + writer.write_num(static_cast(size())); + writer.get_ostr().write(reinterpret_cast(data.data()), + data.size()); + } + + template + inline void tag_array::write_payload(io::stream_writer& writer) const + { + if (size() > io::stream_writer::max_array_len) { + writer.get_ostr().setstate(std::ios::failbit); + throw std::length_error("Generic array is too large for NBT"); + } + writer.write_num(static_cast(size())); + for (T i : data) + writer.write_num(i); + } + + template <> + inline void + tag_array::write_payload(io::stream_writer& writer) const + { + if (size() > io::stream_writer::max_array_len) { + writer.get_ostr().setstate(std::ios::failbit); + throw std::length_error("Long array is too large for NBT"); + } + writer.write_num(static_cast(size())); + for (int64_t i : data) + writer.write_num(i); + } + + // Typedefs that should be used instead of the template tag_array. + typedef tag_array tag_byte_array; + typedef tag_array tag_int_array; + typedef tag_array tag_long_array; + +} // namespace nbt #endif // TAG_ARRAY_H_INCLUDED diff --git a/include/tag_compound.h b/include/tag_compound.h index 60d9d83927..3f342c6d2b 100644 --- a/include/tag_compound.h +++ b/include/tag_compound.h @@ -34,115 +34,152 @@ namespace nbt { -///Tag that contains multiple unordered named tags of arbitrary types -class NBT_EXPORT tag_compound final : public detail::crtp_tag -{ - typedef std::map map_t_; - -public: - //Iterator types - typedef map_t_::iterator iterator; - typedef map_t_::const_iterator const_iterator; - - ///The type of the tag - static constexpr tag_type type = tag_type::Compound; - - ///Constructs an empty compound - tag_compound() {} - - ///Constructs a compound with the given key-value pairs - tag_compound(std::initializer_list> init); - - /** - * @brief Accesses a tag by key with bounds checking - * - * Returns a value to the tag with the specified key, or throws an - * exception if it does not exist. - * @throw std::out_of_range if given key does not exist - */ - value& at(const std::string& key); - const value& at(const std::string& key) const; - - /** - * @brief Accesses a tag by key - * - * Returns a value to the tag with the specified key. If it does not exist, - * creates a new uninitialized entry under the key. - */ - value& operator[](const std::string& key) { return tags[key]; } - - /** - * @brief Inserts or assigns a tag - * - * If the given key already exists, assigns the tag to it. - * Otherwise, it is inserted under the given key. - * @return a pair of the iterator to the value and a bool indicating - * whether the key did not exist - */ - std::pair put(const std::string& key, value_initializer&& val); - - /** - * @brief Inserts a tag if the key does not exist - * @return a pair of the iterator to the value with the key and a bool - * indicating whether the value was actually inserted - */ - std::pair insert(const std::string& key, value_initializer&& val); - - /** - * @brief Constructs and assigns or inserts a tag into the compound - * - * Constructs a new tag of type @c T with the given args and inserts - * or assigns it to the given key. - * @note Unlike std::map::emplace, this will overwrite existing values - * @return a pair of the iterator to the value and a bool indicating - * whether the key did not exist - */ - template - std::pair emplace(const std::string& key, Args&&... args); - - /** - * @brief Erases a tag from the compound - * @return true if a tag was erased - */ - bool erase(const std::string& key); - - ///Returns true if the given key exists in the compound - bool has_key(const std::string& key) const; - ///Returns true if the given key exists and the tag has the given type - bool has_key(const std::string& key, tag_type type) const; - - ///Returns the number of tags in the compound - size_t size() const { return tags.size(); } - - ///Erases all tags from the compound - void clear() { tags.clear(); } - - //Iterators - iterator begin() { return tags.begin(); } - iterator end() { return tags.end(); } - const_iterator begin() const { return tags.begin(); } - const_iterator end() const { return tags.end(); } - const_iterator cbegin() const { return tags.cbegin(); } - const_iterator cend() const { return tags.cend(); } - - void read_payload(io::stream_reader& reader) override; - void write_payload(io::stream_writer& writer) const override; - - friend bool operator==(const tag_compound& lhs, const tag_compound& rhs) - { return lhs.tags == rhs.tags; } - friend bool operator!=(const tag_compound& lhs, const tag_compound& rhs) - { return !(lhs == rhs); } - -private: - map_t_ tags; -}; - -template -std::pair tag_compound::emplace(const std::string& key, Args&&... args) -{ - return put(key, value(make_unique(std::forward(args)...))); -} - -} + /// Tag that contains multiple unordered named tags of arbitrary types + class NBT_EXPORT tag_compound final : public detail::crtp_tag + { + typedef std::map map_t_; + + public: + // Iterator types + typedef map_t_::iterator iterator; + typedef map_t_::const_iterator const_iterator; + + /// The type of the tag + static constexpr tag_type type = tag_type::Compound; + + /// Constructs an empty compound + tag_compound() {} + + /// Constructs a compound with the given key-value pairs + tag_compound( + std::initializer_list> + init); + + /** + * @brief Accesses a tag by key with bounds checking + * + * Returns a value to the tag with the specified key, or throws an + * exception if it does not exist. + * @throw std::out_of_range if given key does not exist + */ + value& at(const std::string& key); + const value& at(const std::string& key) const; + + /** + * @brief Accesses a tag by key + * + * Returns a value to the tag with the specified key. If it does not + * exist, creates a new uninitialized entry under the key. + */ + value& operator[](const std::string& key) + { + return tags[key]; + } + + /** + * @brief Inserts or assigns a tag + * + * If the given key already exists, assigns the tag to it. + * Otherwise, it is inserted under the given key. + * @return a pair of the iterator to the value and a bool indicating + * whether the key did not exist + */ + std::pair put(const std::string& key, + value_initializer&& val); + + /** + * @brief Inserts a tag if the key does not exist + * @return a pair of the iterator to the value with the key and a bool + * indicating whether the value was actually inserted + */ + std::pair insert(const std::string& key, + value_initializer&& val); + + /** + * @brief Constructs and assigns or inserts a tag into the compound + * + * Constructs a new tag of type @c T with the given args and inserts + * or assigns it to the given key. + * @note Unlike std::map::emplace, this will overwrite existing values + * @return a pair of the iterator to the value and a bool indicating + * whether the key did not exist + */ + template + std::pair emplace(const std::string& key, + Args&&... args); + + /** + * @brief Erases a tag from the compound + * @return true if a tag was erased + */ + bool erase(const std::string& key); + + /// Returns true if the given key exists in the compound + bool has_key(const std::string& key) const; + /// Returns true if the given key exists and the tag has the given type + bool has_key(const std::string& key, tag_type type) const; + + /// Returns the number of tags in the compound + size_t size() const + { + return tags.size(); + } + + /// Erases all tags from the compound + void clear() + { + tags.clear(); + } + + // Iterators + iterator begin() + { + return tags.begin(); + } + iterator end() + { + return tags.end(); + } + const_iterator begin() const + { + return tags.begin(); + } + const_iterator end() const + { + return tags.end(); + } + const_iterator cbegin() const + { + return tags.cbegin(); + } + const_iterator cend() const + { + return tags.cend(); + } + + void read_payload(io::stream_reader& reader) override; + void write_payload(io::stream_writer& writer) const override; + + friend bool operator==(const tag_compound& lhs, const tag_compound& rhs) + { + return lhs.tags == rhs.tags; + } + friend bool operator!=(const tag_compound& lhs, const tag_compound& rhs) + { + return !(lhs == rhs); + } + + private: + map_t_ tags; + }; + + template + std::pair + tag_compound::emplace(const std::string& key, Args&&... args) + { + return put(key, value(make_unique(std::forward(args)...))); + } + +} // namespace nbt #endif // TAG_COMPOUND_H_INCLUDED diff --git a/include/tag_list.h b/include/tag_list.h index 556678a59d..eaa41bd65d 100644 --- a/include/tag_list.h +++ b/include/tag_list.h @@ -35,195 +35,235 @@ namespace nbt { -/** - * @brief Tag that contains multiple unnamed tags of the same type - * - * All the tags contained in the list have the same type, which can be queried - * with el_type(). The types of the values contained in the list should not - * be changed to mismatch the element type. - * - * If the list is empty, the type can be undetermined, in which case el_type() - * will return tag_type::Null. The type will then be set when the first tag - * is added to the list. - */ -class NBT_EXPORT tag_list final : public detail::crtp_tag -{ -public: - //Iterator types - typedef std::vector::iterator iterator; - typedef std::vector::const_iterator const_iterator; - - ///The type of the tag - static constexpr tag_type type = tag_type::List; - - /** - * @brief Constructs a list of type T with the given values - * - * Example: @code tag_list::of({3, 4, 5}) @endcode - * @param init list of values from which the elements are constructed - */ - template - static tag_list of(std::initializer_list init); - - /** - * @brief Constructs an empty list - * - * The content type is determined when the first tag is added. - */ - tag_list(): tag_list(tag_type::Null) {} - - ///Constructs an empty list with the given content type - explicit tag_list(tag_type content_type): el_type_(content_type) {} - - ///Constructs a list with the given contents - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - tag_list(std::initializer_list init); - - /** - * @brief Constructs a list with the given contents - * @throw std::invalid_argument if the tags are not all of the same type - */ - tag_list(std::initializer_list init); - - /** - * @brief Accesses a tag by index with bounds checking - * - * Returns a value to the tag at the specified index, or throws an - * exception if it is out of range. - * @throw std::out_of_range if the index is out of range - */ - value& at(size_t i); - const value& at(size_t i) const; - - /** - * @brief Accesses a tag by index - * - * Returns a value to the tag at the specified index. No bounds checking - * is performed. - */ - value& operator[](size_t i) { return tags[i]; } - const value& operator[](size_t i) const { return tags[i]; } - - /** - * @brief Assigns a value at the given index - * @throw std::invalid_argument if the type of the value does not match the list's - * content type - * @throw std::out_of_range if the index is out of range - */ - void set(size_t i, value&& val); - - /** - * @brief Appends the tag to the end of the list - * @throw std::invalid_argument if the type of the tag does not match the list's - * content type - */ - void push_back(value_initializer&& val); - - /** - * @brief Constructs and appends a tag to the end of the list - * @throw std::invalid_argument if the type of the tag does not match the list's - * content type - */ - template - void emplace_back(Args&&... args); - - ///Removes the last element of the list - void pop_back() { tags.pop_back(); } - - ///Returns the content type of the list, or tag_type::Null if undetermined - tag_type el_type() const { return el_type_; } - - ///Returns the number of tags in the list - size_t size() const { return tags.size(); } - - ///Erases all tags from the list. Preserves the content type. - void clear() { tags.clear(); } - - /** - * @brief Erases all tags from the list and changes the content type. - * @param type the new content type. Can be tag_type::Null to leave it undetermined. - */ - void reset(tag_type type = tag_type::Null); - - //Iterators - iterator begin() { return tags.begin(); } - iterator end() { return tags.end(); } - const_iterator begin() const { return tags.begin(); } - const_iterator end() const { return tags.end(); } - const_iterator cbegin() const { return tags.cbegin(); } - const_iterator cend() const { return tags.cend(); } - - /** - * @inheritdoc - * In case of a list of tag_end, the content type will be undetermined. - */ - void read_payload(io::stream_reader& reader) override; - /** - * @inheritdoc - * In case of a list of undetermined content type, the written type will be tag_end. - * @throw std::length_error if the list is too long for NBT - */ - void write_payload(io::stream_writer& writer) const override; - - /** - * @brief Equality comparison for lists - * - * Lists are considered equal if their content types and the contained tags - * are equal. - */ - friend NBT_EXPORT bool operator==(const tag_list& lhs, const tag_list& rhs); - friend NBT_EXPORT bool operator!=(const tag_list& lhs, const tag_list& rhs); - -private: - std::vector tags; - tag_type el_type_; - - /** - * Internally used initialization function that initializes the list with - * tags of type T, with the constructor arguments of each T given by il. - * @param il list of values that are, one by one, given to a constructor of T - */ - template - void init(std::initializer_list il); -}; - -template -tag_list tag_list::of(std::initializer_list il) -{ - tag_list result; - result.init(il); - return result; -} + /** + * @brief Tag that contains multiple unnamed tags of the same type + * + * All the tags contained in the list have the same type, which can be + * queried with el_type(). The types of the values contained in the list + * should not be changed to mismatch the element type. + * + * If the list is empty, the type can be undetermined, in which case + * el_type() will return tag_type::Null. The type will then be set when the + * first tag is added to the list. + */ + class NBT_EXPORT tag_list final : public detail::crtp_tag + { + public: + // Iterator types + typedef std::vector::iterator iterator; + typedef std::vector::const_iterator const_iterator; -template -void tag_list::emplace_back(Args&&... args) -{ - if(el_type_ == tag_type::Null) //set content type if undetermined - el_type_ = T::type; - else if(el_type_ != T::type) - throw std::invalid_argument("The tag type does not match the list's content type"); - tags.emplace_back(make_unique(std::forward(args)...)); -} - -template -void tag_list::init(std::initializer_list init) -{ - el_type_ = T::type; - tags.reserve(init.size()); - for(const Arg& arg: init) - tags.emplace_back(nbt::make_unique(arg)); -} + /// The type of the tag + static constexpr tag_type type = tag_type::List; + + /** + * @brief Constructs a list of type T with the given values + * + * Example: @code tag_list::of({3, 4, 5}) @endcode + * @param init list of values from which the elements are constructed + */ + template static tag_list of(std::initializer_list init); + + /** + * @brief Constructs an empty list + * + * The content type is determined when the first tag is added. + */ + tag_list() : tag_list(tag_type::Null) {} + + /// Constructs an empty list with the given content type + explicit tag_list(tag_type content_type) : el_type_(content_type) {} + + /// Constructs a list with the given contents + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + tag_list(std::initializer_list init); + + /** + * @brief Constructs a list with the given contents + * @throw std::invalid_argument if the tags are not all of the same type + */ + tag_list(std::initializer_list init); + + /** + * @brief Accesses a tag by index with bounds checking + * + * Returns a value to the tag at the specified index, or throws an + * exception if it is out of range. + * @throw std::out_of_range if the index is out of range + */ + value& at(size_t i); + const value& at(size_t i) const; + + /** + * @brief Accesses a tag by index + * + * Returns a value to the tag at the specified index. No bounds checking + * is performed. + */ + value& operator[](size_t i) + { + return tags[i]; + } + const value& operator[](size_t i) const + { + return tags[i]; + } + + /** + * @brief Assigns a value at the given index + * @throw std::invalid_argument if the type of the value does not match + * the list's content type + * @throw std::out_of_range if the index is out of range + */ + void set(size_t i, value&& val); + + /** + * @brief Appends the tag to the end of the list + * @throw std::invalid_argument if the type of the tag does not match + * the list's content type + */ + void push_back(value_initializer&& val); + + /** + * @brief Constructs and appends a tag to the end of the list + * @throw std::invalid_argument if the type of the tag does not match + * the list's content type + */ + template void emplace_back(Args&&... args); + + /// Removes the last element of the list + void pop_back() + { + tags.pop_back(); + } + + /// Returns the content type of the list, or tag_type::Null if + /// undetermined + tag_type el_type() const + { + return el_type_; + } + + /// Returns the number of tags in the list + size_t size() const + { + return tags.size(); + } + + /// Erases all tags from the list. Preserves the content type. + void clear() + { + tags.clear(); + } + + /** + * @brief Erases all tags from the list and changes the content type. + * @param type the new content type. Can be tag_type::Null to leave it + * undetermined. + */ + void reset(tag_type type = tag_type::Null); + + // Iterators + iterator begin() + { + return tags.begin(); + } + iterator end() + { + return tags.end(); + } + const_iterator begin() const + { + return tags.begin(); + } + const_iterator end() const + { + return tags.end(); + } + const_iterator cbegin() const + { + return tags.cbegin(); + } + const_iterator cend() const + { + return tags.cend(); + } + + /** + * @inheritdoc + * In case of a list of tag_end, the content type will be undetermined. + */ + void read_payload(io::stream_reader& reader) override; + /** + * @inheritdoc + * In case of a list of undetermined content type, the written type will + * be tag_end. + * @throw std::length_error if the list is too long for NBT + */ + void write_payload(io::stream_writer& writer) const override; + + /** + * @brief Equality comparison for lists + * + * Lists are considered equal if their content types and the contained + * tags are equal. + */ + friend NBT_EXPORT bool operator==(const tag_list& lhs, + const tag_list& rhs); + friend NBT_EXPORT bool operator!=(const tag_list& lhs, + const tag_list& rhs); + + private: + std::vector tags; + tag_type el_type_; + + /** + * Internally used initialization function that initializes the list + * with tags of type T, with the constructor arguments of each T given + * by il. + * @param il list of values that are, one by one, given to a constructor + * of T + */ + template void init(std::initializer_list il); + }; + + template tag_list tag_list::of(std::initializer_list il) + { + tag_list result; + result.init(il); + return result; + } + + template + void tag_list::emplace_back(Args&&... args) + { + if (el_type_ == tag_type::Null) // set content type if undetermined + el_type_ = T::type; + else if (el_type_ != T::type) + throw std::invalid_argument( + "The tag type does not match the list's content type"); + tags.emplace_back(make_unique(std::forward(args)...)); + } + + template + void tag_list::init(std::initializer_list init) + { + el_type_ = T::type; + tags.reserve(init.size()); + for (const Arg& arg : init) + tags.emplace_back(nbt::make_unique(arg)); + } -} +} // namespace nbt #endif // TAG_LIST_H_INCLUDED diff --git a/include/tag_primitive.h b/include/tag_primitive.h index a2da0cfab9..e1b3fd28ea 100644 --- a/include/tag_primitive.h +++ b/include/tag_primitive.h @@ -36,79 +36,101 @@ namespace nbt { -/** - * @brief Tag that contains an integral or floating-point value - * - * Common class for tag_byte, tag_short, tag_int, tag_long, tag_float and tag_double. - */ -template -class tag_primitive final : public detail::crtp_tag> -{ -public: - ///The type of the value - typedef T value_type; - - ///The type of the tag - static constexpr tag_type type = detail::get_primitive_type::value; - - //Constructor - constexpr tag_primitive(T val = 0) noexcept: value(val) {} - - //Getters - operator T&() { return value; } - constexpr operator T() const { return value; } - constexpr T get() const { return value; } - - //Setters - tag_primitive& operator=(T val) { value = val; return *this; } - void set(T val) { value = val; } - - void read_payload(io::stream_reader& reader) override; - void write_payload(io::stream_writer& writer) const override; - -private: - T value; -}; - -template bool operator==(const tag_primitive& lhs, const tag_primitive& rhs) -{ return lhs.get() == rhs.get(); } -template bool operator!=(const tag_primitive& lhs, const tag_primitive& rhs) -{ return !(lhs == rhs); } - -//Typedefs that should be used instead of the template tag_primitive. -typedef tag_primitive tag_byte; -typedef tag_primitive tag_short; -typedef tag_primitive tag_int; -typedef tag_primitive tag_long; -typedef tag_primitive tag_float; -typedef tag_primitive tag_double; - -//Explicit instantiation declarations -extern template class NBT_EXPORT tag_primitive; -extern template class NBT_EXPORT tag_primitive; -extern template class NBT_EXPORT tag_primitive; -extern template class NBT_EXPORT tag_primitive; -extern template class NBT_EXPORT tag_primitive; -extern template class NBT_EXPORT tag_primitive; - -template -void tag_primitive::read_payload(io::stream_reader& reader) -{ - reader.read_num(value); - if(!reader.get_istr()) - { - std::ostringstream str; - str << "Error reading tag_" << type; - throw io::input_error(str.str()); - } -} - -template -void tag_primitive::write_payload(io::stream_writer& writer) const -{ - writer.write_num(value); -} - -} + /** + * @brief Tag that contains an integral or floating-point value + * + * Common class for tag_byte, tag_short, tag_int, tag_long, tag_float and + * tag_double. + */ + template + class tag_primitive final : public detail::crtp_tag> + { + public: + /// The type of the value + typedef T value_type; + + /// The type of the tag + static constexpr tag_type type = detail::get_primitive_type::value; + + // Constructor + constexpr tag_primitive(T val = 0) noexcept : value(val) {} + + // Getters + operator T&() + { + return value; + } + constexpr operator T() const + { + return value; + } + constexpr T get() const + { + return value; + } + + // Setters + tag_primitive& operator=(T val) + { + value = val; + return *this; + } + void set(T val) + { + value = val; + } + + void read_payload(io::stream_reader& reader) override; + void write_payload(io::stream_writer& writer) const override; + + private: + T value; + }; + + template + bool operator==(const tag_primitive& lhs, const tag_primitive& rhs) + { + return lhs.get() == rhs.get(); + } + template + bool operator!=(const tag_primitive& lhs, const tag_primitive& rhs) + { + return !(lhs == rhs); + } + + // Typedefs that should be used instead of the template tag_primitive. + typedef tag_primitive tag_byte; + typedef tag_primitive tag_short; + typedef tag_primitive tag_int; + typedef tag_primitive tag_long; + typedef tag_primitive tag_float; + typedef tag_primitive tag_double; + + // Explicit instantiation declarations + extern template class NBT_EXPORT tag_primitive; + extern template class NBT_EXPORT tag_primitive; + extern template class NBT_EXPORT tag_primitive; + extern template class NBT_EXPORT tag_primitive; + extern template class NBT_EXPORT tag_primitive; + extern template class NBT_EXPORT tag_primitive; + + template + void tag_primitive::read_payload(io::stream_reader& reader) + { + reader.read_num(value); + if (!reader.get_istr()) { + std::ostringstream str; + str << "Error reading tag_" << type; + throw io::input_error(str.str()); + } + } + + template + void tag_primitive::write_payload(io::stream_writer& writer) const + { + writer.write_num(value); + } + +} // namespace nbt #endif // TAG_PRIMITIVE_H_INCLUDED diff --git a/include/tag_string.h b/include/tag_string.h index 35ddd371aa..ec08e3d7d5 100644 --- a/include/tag_string.h +++ b/include/tag_string.h @@ -32,47 +32,78 @@ namespace nbt { -///Tag that contains a UTF-8 string -class NBT_EXPORT tag_string final : public detail::crtp_tag -{ -public: - ///The type of the tag - static constexpr tag_type type = tag_type::String; + /// Tag that contains a UTF-8 string + class NBT_EXPORT tag_string final : public detail::crtp_tag + { + public: + /// The type of the tag + static constexpr tag_type type = tag_type::String; - //Constructors - tag_string() {} - tag_string(const std::string& str): value(str) {} - tag_string(std::string&& str) noexcept: value(std::move(str)) {} - tag_string(const char* str): value(str) {} + // Constructors + tag_string() {} + tag_string(const std::string& str) : value(str) {} + tag_string(std::string&& str) noexcept : value(std::move(str)) {} + tag_string(const char* str) : value(str) {} - //Getters - operator std::string&() { return value; } - operator const std::string&() const { return value; } - const std::string& get() const { return value; } + // Getters + operator std::string&() + { + return value; + } + operator const std::string&() const + { + return value; + } + const std::string& get() const + { + return value; + } - //Setters - tag_string& operator=(const std::string& str) { value = str; return *this; } - tag_string& operator=(std::string&& str) { value = std::move(str); return *this; } - tag_string& operator=(const char* str) { value = str; return *this; } - void set(const std::string& str) { value = str; } - void set(std::string&& str) { value = std::move(str); } + // Setters + tag_string& operator=(const std::string& str) + { + value = str; + return *this; + } + tag_string& operator=(std::string&& str) + { + value = std::move(str); + return *this; + } + tag_string& operator=(const char* str) + { + value = str; + return *this; + } + void set(const std::string& str) + { + value = str; + } + void set(std::string&& str) + { + value = std::move(str); + } - void read_payload(io::stream_reader& reader) override; - /** - * @inheritdoc - * @throw std::length_error if the string is too long for NBT - */ - void write_payload(io::stream_writer& writer) const override; + void read_payload(io::stream_reader& reader) override; + /** + * @inheritdoc + * @throw std::length_error if the string is too long for NBT + */ + void write_payload(io::stream_writer& writer) const override; -private: - std::string value; -}; + private: + std::string value; + }; -inline bool operator==(const tag_string& lhs, const tag_string& rhs) -{ return lhs.get() == rhs.get(); } -inline bool operator!=(const tag_string& lhs, const tag_string& rhs) -{ return !(lhs == rhs); } + inline bool operator==(const tag_string& lhs, const tag_string& rhs) + { + return lhs.get() == rhs.get(); + } + inline bool operator!=(const tag_string& lhs, const tag_string& rhs) + { + return !(lhs == rhs); + } -} +} // namespace nbt #endif // TAG_STRING_H_INCLUDED diff --git a/include/tagfwd.h b/include/tagfwd.h index a302fe327b..7459e7ba08 100644 --- a/include/tagfwd.h +++ b/include/tagfwd.h @@ -33,26 +33,26 @@ namespace nbt { -class tag; + class tag; -template class tag_primitive; -typedef tag_primitive tag_byte; -typedef tag_primitive tag_short; -typedef tag_primitive tag_int; -typedef tag_primitive tag_long; -typedef tag_primitive tag_float; -typedef tag_primitive tag_double; + template class tag_primitive; + typedef tag_primitive tag_byte; + typedef tag_primitive tag_short; + typedef tag_primitive tag_int; + typedef tag_primitive tag_long; + typedef tag_primitive tag_float; + typedef tag_primitive tag_double; -class tag_string; + class tag_string; -template class tag_array; -typedef tag_array tag_byte_array; -typedef tag_array tag_int_array; -typedef tag_array tag_long_array; + template class tag_array; + typedef tag_array tag_byte_array; + typedef tag_array tag_int_array; + typedef tag_array tag_long_array; -class tag_list; -class tag_compound; + class tag_list; + class tag_compound; -} +} // namespace nbt #endif // TAGFWD_H_INCLUDED diff --git a/include/text/json_formatter.h b/include/text/json_formatter.h index 2f9246939b..923970f446 100644 --- a/include/text/json_formatter.h +++ b/include/text/json_formatter.h @@ -32,22 +32,23 @@ namespace nbt { -namespace text -{ + namespace text + { -/** - * @brief Prints tags in a JSON-like syntax into a stream - * - * @todo Make it configurable and able to produce actual standard-conformant JSON - */ -class NBT_EXPORT json_formatter -{ -public: - json_formatter() {} - void print(std::ostream& os, const tag& t) const; -}; + /** + * @brief Prints tags in a JSON-like syntax into a stream + * + * @todo Make it configurable and able to produce actual + * standard-conformant JSON + */ + class NBT_EXPORT json_formatter + { + public: + json_formatter() {} + void print(std::ostream& os, const tag& t) const; + }; -} -} + } // namespace text +} // namespace nbt #endif // JSON_FORMATTER_H_INCLUDED diff --git a/include/value.h b/include/value.h index 36b55ffa57..08a23ab111 100644 --- a/include/value.h +++ b/include/value.h @@ -33,195 +33,223 @@ namespace nbt { -/** - * @brief Contains an NBT value of fixed type - * - * This class is a convenience wrapper for @c std::unique_ptr. - * A value can contain any kind of tag or no tag (nullptr) and provides - * operations for handling tags of which the type is not known at compile time. - * Assignment or the set method on a value with no tag will fill in the value. - * - * The rationale for the existance of this class is to provide a type-erasured - * means of storing tags, especially when they are contained in tag_compound - * or tag_list. The alternative would be directly using @c std::unique_ptr - * and @c tag&, which is how it was done in libnbt++1. The main drawback is that - * it becomes very cumbersome to deal with tags of unknown type. - * - * For example, in this case it would not be possible to allow a syntax like - * compound["foo"] = 42. If the key "foo" does not exist beforehand, - * the left hand side could not have any sensible value if it was of type - * @c tag&. - * Firstly, the compound tag would have to create a new tag_int there, but it - * cannot know that the new tag is going to be assigned an integer. - * Also, if the type was @c tag& and it allowed assignment of integers, that - * would mean the tag base class has assignments and conversions like this. - * Which means that all other tag classes would inherit them from the base - * class, even though it does not make any sense to allow converting a - * tag_compound into an integer. Attempts like this should be caught at - * compile time. - * - * This is why all the syntactic sugar for tags is contained in the value class - * while the tag class only contains common operations for all tag types. - */ -class NBT_EXPORT value -{ -public: - //Constructors - value() noexcept {} - explicit value(std::unique_ptr&& t) noexcept: tag_(std::move(t)) {} - explicit value(tag&& t); - - //Moving - value(value&&) noexcept = default; - value& operator=(value&&) noexcept = default; - - //Copying - explicit value(const value& rhs); - value& operator=(const value& rhs); - - /** - * @brief Assigns the given value to the tag if the type matches - * @throw std::bad_cast if the type of @c t is not the same as the type - * of this value - */ - value& operator=(tag&& t); - void set(tag&& t); - - //Conversion to tag - /** - * @brief Returns the contained tag - * - * If the value is uninitialized, the behavior is undefined. - */ - operator tag&() { return get(); } - operator const tag&() const { return get(); } - tag& get() { return *tag_; } - const tag& get() const { return *tag_; } - - /** - * @brief Returns a reference to the contained tag as an instance of T - * @throw std::bad_cast if the tag is not of type T - */ - template - T& as(); - template - const T& as() const; - - //Assignment of primitives and string - /** - * @brief Assigns the given value to the tag if the type is compatible - * @throw std::bad_cast if the value is not convertible to the tag type - * via a widening conversion - */ - value& operator=(int8_t val); - value& operator=(int16_t val); - value& operator=(int32_t val); - value& operator=(int64_t val); - value& operator=(float val); - value& operator=(double val); - - /** - * @brief Assigns the given string to the tag if it is a tag_string - * @throw std::bad_cast if the contained tag is not a tag_string - */ - value& operator=(const std::string& str); - value& operator=(std::string&& str); - - //Conversions to primitives and string - /** - * @brief Returns the contained value if the type is compatible - * @throw std::bad_cast if the tag type is not convertible to the desired - * type via a widening conversion - */ - explicit operator int8_t() const; - explicit operator int16_t() const; - explicit operator int32_t() const; - explicit operator int64_t() const; - explicit operator float() const; - explicit operator double() const; - - /** - * @brief Returns the contained string if the type is tag_string - * - * If the value is uninitialized, the behavior is undefined. - * @throw std::bad_cast if the tag type is not tag_string - */ - explicit operator const std::string&() const; - - ///Returns true if the value is not uninitialized - explicit operator bool() const { return tag_ != nullptr; } - - /** - * @brief In case of a tag_compound, accesses a tag by key with bounds checking - * - * If the value is uninitialized, the behavior is undefined. - * @throw std::bad_cast if the tag type is not tag_compound - * @throw std::out_of_range if given key does not exist - * @sa tag_compound::at - */ - value& at(const std::string& key); - const value& at(const std::string& key) const; - - /** - * @brief In case of a tag_compound, accesses a tag by key - * - * If the value is uninitialized, the behavior is undefined. - * @throw std::bad_cast if the tag type is not tag_compound - * @sa tag_compound::operator[] - */ - value& operator[](const std::string& key); - value& operator[](const char* key); //need this overload because of conflict with built-in operator[] - - /** - * @brief In case of a tag_list, accesses a tag by index with bounds checking - * - * If the value is uninitialized, the behavior is undefined. - * @throw std::bad_cast if the tag type is not tag_list - * @throw std::out_of_range if the index is out of range - * @sa tag_list::at - */ - value& at(size_t i); - const value& at(size_t i) const; - - /** - * @brief In case of a tag_list, accesses a tag by index - * - * No bounds checking is performed. If the value is uninitialized, the - * behavior is undefined. - * @throw std::bad_cast if the tag type is not tag_list - * @sa tag_list::operator[] - */ - value& operator[](size_t i); - const value& operator[](size_t i) const; - - ///Returns a reference to the underlying std::unique_ptr - std::unique_ptr& get_ptr() { return tag_; } - const std::unique_ptr& get_ptr() const { return tag_; } - ///Resets the underlying std::unique_ptr to a different value - void set_ptr(std::unique_ptr&& t) { tag_ = std::move(t); } - - ///@sa tag::get_type - tag_type get_type() const; - - friend NBT_EXPORT bool operator==(const value& lhs, const value& rhs); - friend NBT_EXPORT bool operator!=(const value& lhs, const value& rhs); - -private: - std::unique_ptr tag_; -}; - -template -T& value::as() -{ - return tag_->as(); -} - -template -const T& value::as() const -{ - return tag_->as(); -} - -} + /** + * @brief Contains an NBT value of fixed type + * + * This class is a convenience wrapper for @c std::unique_ptr. + * A value can contain any kind of tag or no tag (nullptr) and provides + * operations for handling tags of which the type is not known at compile + * time. Assignment or the set method on a value with no tag will fill in + * the value. + * + * The rationale for the existance of this class is to provide a + * type-erasured means of storing tags, especially when they are contained + * in tag_compound or tag_list. The alternative would be directly using @c + * std::unique_ptr and @c tag&, which is how it was done in libnbt++1. + * The main drawback is that it becomes very cumbersome to deal with tags of + * unknown type. + * + * For example, in this case it would not be possible to allow a syntax like + * compound["foo"] = 42. If the key "foo" does not exist + * beforehand, the left hand side could not have any sensible value if it + * was of type + * @c tag&. + * Firstly, the compound tag would have to create a new tag_int there, but + * it cannot know that the new tag is going to be assigned an integer. Also, + * if the type was @c tag& and it allowed assignment of integers, that would + * mean the tag base class has assignments and conversions like this. Which + * means that all other tag classes would inherit them from the base class, + * even though it does not make any sense to allow converting a tag_compound + * into an integer. Attempts like this should be caught at compile time. + * + * This is why all the syntactic sugar for tags is contained in the value + * class while the tag class only contains common operations for all tag + * types. + */ + class NBT_EXPORT value + { + public: + // Constructors + value() noexcept {} + explicit value(std::unique_ptr&& t) noexcept : tag_(std::move(t)) + { + } + explicit value(tag&& t); + + // Moving + value(value&&) noexcept = default; + value& operator=(value&&) noexcept = default; + + // Copying + explicit value(const value& rhs); + value& operator=(const value& rhs); + + /** + * @brief Assigns the given value to the tag if the type matches + * @throw std::bad_cast if the type of @c t is not the same as the type + * of this value + */ + value& operator=(tag&& t); + void set(tag&& t); + + // Conversion to tag + /** + * @brief Returns the contained tag + * + * If the value is uninitialized, the behavior is undefined. + */ + operator tag&() + { + return get(); + } + operator const tag&() const + { + return get(); + } + tag& get() + { + return *tag_; + } + const tag& get() const + { + return *tag_; + } + + /** + * @brief Returns a reference to the contained tag as an instance of T + * @throw std::bad_cast if the tag is not of type T + */ + template T& as(); + template const T& as() const; + + // Assignment of primitives and string + /** + * @brief Assigns the given value to the tag if the type is compatible + * @throw std::bad_cast if the value is not convertible to the tag type + * via a widening conversion + */ + value& operator=(int8_t val); + value& operator=(int16_t val); + value& operator=(int32_t val); + value& operator=(int64_t val); + value& operator=(float val); + value& operator=(double val); + + /** + * @brief Assigns the given string to the tag if it is a tag_string + * @throw std::bad_cast if the contained tag is not a tag_string + */ + value& operator=(const std::string& str); + value& operator=(std::string&& str); + + // Conversions to primitives and string + /** + * @brief Returns the contained value if the type is compatible + * @throw std::bad_cast if the tag type is not convertible to the + * desired type via a widening conversion + */ + explicit operator int8_t() const; + explicit operator int16_t() const; + explicit operator int32_t() const; + explicit operator int64_t() const; + explicit operator float() const; + explicit operator double() const; + + /** + * @brief Returns the contained string if the type is tag_string + * + * If the value is uninitialized, the behavior is undefined. + * @throw std::bad_cast if the tag type is not tag_string + */ + explicit operator const std::string&() const; + + /// Returns true if the value is not uninitialized + explicit operator bool() const + { + return tag_ != nullptr; + } + + /** + * @brief In case of a tag_compound, accesses a tag by key with bounds + * checking + * + * If the value is uninitialized, the behavior is undefined. + * @throw std::bad_cast if the tag type is not tag_compound + * @throw std::out_of_range if given key does not exist + * @sa tag_compound::at + */ + value& at(const std::string& key); + const value& at(const std::string& key) const; + + /** + * @brief In case of a tag_compound, accesses a tag by key + * + * If the value is uninitialized, the behavior is undefined. + * @throw std::bad_cast if the tag type is not tag_compound + * @sa tag_compound::operator[] + */ + value& operator[](const std::string& key); + value& operator[](const char* key); // need this overload because of + // conflict with built-in operator[] + + /** + * @brief In case of a tag_list, accesses a tag by index with bounds + * checking + * + * If the value is uninitialized, the behavior is undefined. + * @throw std::bad_cast if the tag type is not tag_list + * @throw std::out_of_range if the index is out of range + * @sa tag_list::at + */ + value& at(size_t i); + const value& at(size_t i) const; + + /** + * @brief In case of a tag_list, accesses a tag by index + * + * No bounds checking is performed. If the value is uninitialized, the + * behavior is undefined. + * @throw std::bad_cast if the tag type is not tag_list + * @sa tag_list::operator[] + */ + value& operator[](size_t i); + const value& operator[](size_t i) const; + + /// Returns a reference to the underlying std::unique_ptr + std::unique_ptr& get_ptr() + { + return tag_; + } + const std::unique_ptr& get_ptr() const + { + return tag_; + } + /// Resets the underlying std::unique_ptr to a different value + void set_ptr(std::unique_ptr&& t) + { + tag_ = std::move(t); + } + + ///@sa tag::get_type + tag_type get_type() const; + + friend NBT_EXPORT bool operator==(const value& lhs, const value& rhs); + friend NBT_EXPORT bool operator!=(const value& lhs, const value& rhs); + + private: + std::unique_ptr tag_; + }; + + template T& value::as() + { + return tag_->as(); + } + + template const T& value::as() const + { + return tag_->as(); + } + +} // namespace nbt #endif // TAG_REF_PROXY_H_INCLUDED diff --git a/include/value_initializer.h b/include/value_initializer.h index 7bcc424de2..ad83dc16ee 100644 --- a/include/value_initializer.h +++ b/include/value_initializer.h @@ -31,41 +31,45 @@ namespace nbt { -/** - * @brief Helper class for implicitly constructing value objects - * - * This type is a subclass of @ref value. However the only difference to value - * is that this class has additional constructors which allow implicit - * conversion of various types to value objects. These constructors are not - * part of the value class itself because implicit conversions like this - * (especially from @c tag&& to @c value) can cause problems and ambiguities - * in some cases. - * - * value_initializer is especially useful as function parameter type, it will - * allow convenient conversion of various values to tags on function call. - * - * As value_initializer objects are in no way different than value objects, - * they can just be converted to value after construction. - */ -class NBT_EXPORT value_initializer : public value -{ -public: - value_initializer(std::unique_ptr&& t) noexcept: value(std::move(t)) {} - value_initializer(std::nullptr_t) noexcept : value(nullptr) {} - value_initializer(value&& val) noexcept : value(std::move(val)) {} - value_initializer(tag&& t) : value(std::move(t)) {} + /** + * @brief Helper class for implicitly constructing value objects + * + * This type is a subclass of @ref value. However the only difference to + * value is that this class has additional constructors which allow implicit + * conversion of various types to value objects. These constructors are not + * part of the value class itself because implicit conversions like this + * (especially from @c tag&& to @c value) can cause problems and ambiguities + * in some cases. + * + * value_initializer is especially useful as function parameter type, it + * will allow convenient conversion of various values to tags on function + * call. + * + * As value_initializer objects are in no way different than value objects, + * they can just be converted to value after construction. + */ + class NBT_EXPORT value_initializer : public value + { + public: + value_initializer(std::unique_ptr&& t) noexcept + : value(std::move(t)) + { + } + value_initializer(std::nullptr_t) noexcept : value(nullptr) {} + value_initializer(value&& val) noexcept : value(std::move(val)) {} + value_initializer(tag&& t) : value(std::move(t)) {} - value_initializer(int8_t val); - value_initializer(int16_t val); - value_initializer(int32_t val); - value_initializer(int64_t val); - value_initializer(float val); - value_initializer(double val); - value_initializer(const std::string& str); - value_initializer(std::string&& str); - value_initializer(const char* str); -}; + value_initializer(int8_t val); + value_initializer(int16_t val); + value_initializer(int32_t val); + value_initializer(int64_t val); + value_initializer(float val); + value_initializer(double val); + value_initializer(const std::string& str); + value_initializer(std::string&& str); + value_initializer(const char* str); + }; -} +} // namespace nbt #endif // VALUE_INITIALIZER_H_INCLUDED diff --git a/src/endian_str.cpp b/src/endian_str.cpp index c6de84201d..e9553cd9d3 100644 --- a/src/endian_str.cpp +++ b/src/endian_str.cpp @@ -33,256 +33,268 @@ static_assert(sizeof(double) == 8, "Assuming that a double is 8 byte long"); namespace endian { -namespace //anonymous -{ - void pun_int_to_float(float& f, uint32_t i) - { - //Yes we need to do it this way to avoid undefined behavior - memcpy(&f, &i, 4); - } - - uint32_t pun_float_to_int(float f) - { - uint32_t ret; - memcpy(&ret, &f, 4); - return ret; - } - - void pun_int_to_double(double& d, uint64_t i) - { - memcpy(&d, &i, 8); - } - - uint64_t pun_double_to_int(double f) - { - uint64_t ret; - memcpy(&ret, &f, 8); - return ret; - } -} - -//------------------------------------------------------------------------------ - -void read_little(std::istream& is, uint8_t& x) -{ - is.get(reinterpret_cast(x)); -} - -void read_little(std::istream& is, uint16_t& x) -{ - uint8_t tmp[2]; - is.read(reinterpret_cast(tmp), 2); - x = uint16_t(tmp[0]) - | (uint16_t(tmp[1]) << 8); -} - -void read_little(std::istream& is, uint32_t& x) -{ - uint8_t tmp[4]; - is.read(reinterpret_cast(tmp), 4); - x = uint32_t(tmp[0]) - | (uint32_t(tmp[1]) << 8) - | (uint32_t(tmp[2]) << 16) - | (uint32_t(tmp[3]) << 24); -} - -void read_little(std::istream& is, uint64_t& x) -{ - uint8_t tmp[8]; - is.read(reinterpret_cast(tmp), 8); - x = uint64_t(tmp[0]) - | (uint64_t(tmp[1]) << 8) - | (uint64_t(tmp[2]) << 16) - | (uint64_t(tmp[3]) << 24) - | (uint64_t(tmp[4]) << 32) - | (uint64_t(tmp[5]) << 40) - | (uint64_t(tmp[6]) << 48) - | (uint64_t(tmp[7]) << 56); -} - -void read_little(std::istream& is, int8_t & x) { read_little(is, reinterpret_cast(x)); } -void read_little(std::istream& is, int16_t& x) { read_little(is, reinterpret_cast(x)); } -void read_little(std::istream& is, int32_t& x) { read_little(is, reinterpret_cast(x)); } -void read_little(std::istream& is, int64_t& x) { read_little(is, reinterpret_cast(x)); } - -void read_little(std::istream& is, float& x) -{ - uint32_t tmp; - read_little(is, tmp); - pun_int_to_float(x, tmp); -} - -void read_little(std::istream& is, double& x) -{ - uint64_t tmp; - read_little(is, tmp); - pun_int_to_double(x, tmp); -} - -//------------------------------------------------------------------------------ - -void read_big(std::istream& is, uint8_t& x) -{ - is.read(reinterpret_cast(&x), 1); -} - -void read_big(std::istream& is, uint16_t& x) -{ - uint8_t tmp[2]; - is.read(reinterpret_cast(tmp), 2); - x = uint16_t(tmp[1]) - | (uint16_t(tmp[0]) << 8); -} - -void read_big(std::istream& is, uint32_t& x) -{ - uint8_t tmp[4]; - is.read(reinterpret_cast(tmp), 4); - x = uint32_t(tmp[3]) - | (uint32_t(tmp[2]) << 8) - | (uint32_t(tmp[1]) << 16) - | (uint32_t(tmp[0]) << 24); -} - -void read_big(std::istream& is, uint64_t& x) -{ - uint8_t tmp[8]; - is.read(reinterpret_cast(tmp), 8); - x = uint64_t(tmp[7]) - | (uint64_t(tmp[6]) << 8) - | (uint64_t(tmp[5]) << 16) - | (uint64_t(tmp[4]) << 24) - | (uint64_t(tmp[3]) << 32) - | (uint64_t(tmp[2]) << 40) - | (uint64_t(tmp[1]) << 48) - | (uint64_t(tmp[0]) << 56); -} - -void read_big(std::istream& is, int8_t & x) { read_big(is, reinterpret_cast(x)); } -void read_big(std::istream& is, int16_t& x) { read_big(is, reinterpret_cast(x)); } -void read_big(std::istream& is, int32_t& x) { read_big(is, reinterpret_cast(x)); } -void read_big(std::istream& is, int64_t& x) { read_big(is, reinterpret_cast(x)); } - -void read_big(std::istream& is, float& x) -{ - uint32_t tmp; - read_big(is, tmp); - pun_int_to_float(x, tmp); -} - -void read_big(std::istream& is, double& x) -{ - uint64_t tmp; - read_big(is, tmp); - pun_int_to_double(x, tmp); -} - -//------------------------------------------------------------------------------ - -void write_little(std::ostream& os, uint8_t x) -{ - os.put(x); -} - -void write_little(std::ostream& os, uint16_t x) -{ - uint8_t tmp[2] { - uint8_t(x), - uint8_t(x >> 8)}; - os.write(reinterpret_cast(tmp), 2); -} - -void write_little(std::ostream& os, uint32_t x) -{ - uint8_t tmp[4] { - uint8_t(x), - uint8_t(x >> 8), - uint8_t(x >> 16), - uint8_t(x >> 24)}; - os.write(reinterpret_cast(tmp), 4); -} - -void write_little(std::ostream& os, uint64_t x) -{ - uint8_t tmp[8] { - uint8_t(x), - uint8_t(x >> 8), - uint8_t(x >> 16), - uint8_t(x >> 24), - uint8_t(x >> 32), - uint8_t(x >> 40), - uint8_t(x >> 48), - uint8_t(x >> 56)}; - os.write(reinterpret_cast(tmp), 8); -} - -void write_little(std::ostream& os, int8_t x) { write_little(os, static_cast(x)); } -void write_little(std::ostream& os, int16_t x) { write_little(os, static_cast(x)); } -void write_little(std::ostream& os, int32_t x) { write_little(os, static_cast(x)); } -void write_little(std::ostream& os, int64_t x) { write_little(os, static_cast(x)); } - -void write_little(std::ostream& os, float x) -{ - write_little(os, pun_float_to_int(x)); -} - -void write_little(std::ostream& os, double x) -{ - write_little(os, pun_double_to_int(x)); -} - -//------------------------------------------------------------------------------ - -void write_big(std::ostream& os, uint8_t x) -{ - os.put(x); -} - -void write_big(std::ostream& os, uint16_t x) -{ - uint8_t tmp[2] { - uint8_t(x >> 8), - uint8_t(x)}; - os.write(reinterpret_cast(tmp), 2); -} - -void write_big(std::ostream& os, uint32_t x) -{ - uint8_t tmp[4] { - uint8_t(x >> 24), - uint8_t(x >> 16), - uint8_t(x >> 8), - uint8_t(x)}; - os.write(reinterpret_cast(tmp), 4); -} - -void write_big(std::ostream& os, uint64_t x) -{ - uint8_t tmp[8] { - uint8_t(x >> 56), - uint8_t(x >> 48), - uint8_t(x >> 40), - uint8_t(x >> 32), - uint8_t(x >> 24), - uint8_t(x >> 16), - uint8_t(x >> 8), - uint8_t(x)}; - os.write(reinterpret_cast(tmp), 8); -} - -void write_big(std::ostream& os, int8_t x) { write_big(os, static_cast(x)); } -void write_big(std::ostream& os, int16_t x) { write_big(os, static_cast(x)); } -void write_big(std::ostream& os, int32_t x) { write_big(os, static_cast(x)); } -void write_big(std::ostream& os, int64_t x) { write_big(os, static_cast(x)); } - -void write_big(std::ostream& os, float x) -{ - write_big(os, pun_float_to_int(x)); -} - -void write_big(std::ostream& os, double x) -{ - write_big(os, pun_double_to_int(x)); -} - -} + namespace // anonymous + { + void pun_int_to_float(float& f, uint32_t i) + { + // Yes we need to do it this way to avoid undefined behavior + memcpy(&f, &i, 4); + } + + uint32_t pun_float_to_int(float f) + { + uint32_t ret; + memcpy(&ret, &f, 4); + return ret; + } + + void pun_int_to_double(double& d, uint64_t i) + { + memcpy(&d, &i, 8); + } + + uint64_t pun_double_to_int(double f) + { + uint64_t ret; + memcpy(&ret, &f, 8); + return ret; + } + } // namespace + + //------------------------------------------------------------------------------ + + void read_little(std::istream& is, uint8_t& x) + { + is.get(reinterpret_cast(x)); + } + + void read_little(std::istream& is, uint16_t& x) + { + uint8_t tmp[2]; + is.read(reinterpret_cast(tmp), 2); + x = uint16_t(tmp[0]) | (uint16_t(tmp[1]) << 8); + } + + void read_little(std::istream& is, uint32_t& x) + { + uint8_t tmp[4]; + is.read(reinterpret_cast(tmp), 4); + x = uint32_t(tmp[0]) | (uint32_t(tmp[1]) << 8) | + (uint32_t(tmp[2]) << 16) | (uint32_t(tmp[3]) << 24); + } + + void read_little(std::istream& is, uint64_t& x) + { + uint8_t tmp[8]; + is.read(reinterpret_cast(tmp), 8); + x = uint64_t(tmp[0]) | (uint64_t(tmp[1]) << 8) | + (uint64_t(tmp[2]) << 16) | (uint64_t(tmp[3]) << 24) | + (uint64_t(tmp[4]) << 32) | (uint64_t(tmp[5]) << 40) | + (uint64_t(tmp[6]) << 48) | (uint64_t(tmp[7]) << 56); + } + + void read_little(std::istream& is, int8_t& x) + { + read_little(is, reinterpret_cast(x)); + } + void read_little(std::istream& is, int16_t& x) + { + read_little(is, reinterpret_cast(x)); + } + void read_little(std::istream& is, int32_t& x) + { + read_little(is, reinterpret_cast(x)); + } + void read_little(std::istream& is, int64_t& x) + { + read_little(is, reinterpret_cast(x)); + } + + void read_little(std::istream& is, float& x) + { + uint32_t tmp; + read_little(is, tmp); + pun_int_to_float(x, tmp); + } + + void read_little(std::istream& is, double& x) + { + uint64_t tmp; + read_little(is, tmp); + pun_int_to_double(x, tmp); + } + + //------------------------------------------------------------------------------ + + void read_big(std::istream& is, uint8_t& x) + { + is.read(reinterpret_cast(&x), 1); + } + + void read_big(std::istream& is, uint16_t& x) + { + uint8_t tmp[2]; + is.read(reinterpret_cast(tmp), 2); + x = uint16_t(tmp[1]) | (uint16_t(tmp[0]) << 8); + } + + void read_big(std::istream& is, uint32_t& x) + { + uint8_t tmp[4]; + is.read(reinterpret_cast(tmp), 4); + x = uint32_t(tmp[3]) | (uint32_t(tmp[2]) << 8) | + (uint32_t(tmp[1]) << 16) | (uint32_t(tmp[0]) << 24); + } + + void read_big(std::istream& is, uint64_t& x) + { + uint8_t tmp[8]; + is.read(reinterpret_cast(tmp), 8); + x = uint64_t(tmp[7]) | (uint64_t(tmp[6]) << 8) | + (uint64_t(tmp[5]) << 16) | (uint64_t(tmp[4]) << 24) | + (uint64_t(tmp[3]) << 32) | (uint64_t(tmp[2]) << 40) | + (uint64_t(tmp[1]) << 48) | (uint64_t(tmp[0]) << 56); + } + + void read_big(std::istream& is, int8_t& x) + { + read_big(is, reinterpret_cast(x)); + } + void read_big(std::istream& is, int16_t& x) + { + read_big(is, reinterpret_cast(x)); + } + void read_big(std::istream& is, int32_t& x) + { + read_big(is, reinterpret_cast(x)); + } + void read_big(std::istream& is, int64_t& x) + { + read_big(is, reinterpret_cast(x)); + } + + void read_big(std::istream& is, float& x) + { + uint32_t tmp; + read_big(is, tmp); + pun_int_to_float(x, tmp); + } + + void read_big(std::istream& is, double& x) + { + uint64_t tmp; + read_big(is, tmp); + pun_int_to_double(x, tmp); + } + + //------------------------------------------------------------------------------ + + void write_little(std::ostream& os, uint8_t x) + { + os.put(x); + } + + void write_little(std::ostream& os, uint16_t x) + { + uint8_t tmp[2]{uint8_t(x), uint8_t(x >> 8)}; + os.write(reinterpret_cast(tmp), 2); + } + + void write_little(std::ostream& os, uint32_t x) + { + uint8_t tmp[4]{uint8_t(x), uint8_t(x >> 8), uint8_t(x >> 16), + uint8_t(x >> 24)}; + os.write(reinterpret_cast(tmp), 4); + } + + void write_little(std::ostream& os, uint64_t x) + { + uint8_t tmp[8]{uint8_t(x), uint8_t(x >> 8), uint8_t(x >> 16), + uint8_t(x >> 24), uint8_t(x >> 32), uint8_t(x >> 40), + uint8_t(x >> 48), uint8_t(x >> 56)}; + os.write(reinterpret_cast(tmp), 8); + } + + void write_little(std::ostream& os, int8_t x) + { + write_little(os, static_cast(x)); + } + void write_little(std::ostream& os, int16_t x) + { + write_little(os, static_cast(x)); + } + void write_little(std::ostream& os, int32_t x) + { + write_little(os, static_cast(x)); + } + void write_little(std::ostream& os, int64_t x) + { + write_little(os, static_cast(x)); + } + + void write_little(std::ostream& os, float x) + { + write_little(os, pun_float_to_int(x)); + } + + void write_little(std::ostream& os, double x) + { + write_little(os, pun_double_to_int(x)); + } + + //------------------------------------------------------------------------------ + + void write_big(std::ostream& os, uint8_t x) + { + os.put(x); + } + + void write_big(std::ostream& os, uint16_t x) + { + uint8_t tmp[2]{uint8_t(x >> 8), uint8_t(x)}; + os.write(reinterpret_cast(tmp), 2); + } + + void write_big(std::ostream& os, uint32_t x) + { + uint8_t tmp[4]{uint8_t(x >> 24), uint8_t(x >> 16), uint8_t(x >> 8), + uint8_t(x)}; + os.write(reinterpret_cast(tmp), 4); + } + + void write_big(std::ostream& os, uint64_t x) + { + uint8_t tmp[8]{uint8_t(x >> 56), uint8_t(x >> 48), uint8_t(x >> 40), + uint8_t(x >> 32), uint8_t(x >> 24), uint8_t(x >> 16), + uint8_t(x >> 8), uint8_t(x)}; + os.write(reinterpret_cast(tmp), 8); + } + + void write_big(std::ostream& os, int8_t x) + { + write_big(os, static_cast(x)); + } + void write_big(std::ostream& os, int16_t x) + { + write_big(os, static_cast(x)); + } + void write_big(std::ostream& os, int32_t x) + { + write_big(os, static_cast(x)); + } + void write_big(std::ostream& os, int64_t x) + { + write_big(os, static_cast(x)); + } + + void write_big(std::ostream& os, float x) + { + write_big(os, pun_float_to_int(x)); + } + + void write_big(std::ostream& os, double x) + { + write_big(os, pun_double_to_int(x)); + } + +} // namespace endian diff --git a/src/io/izlibstream.cpp b/src/io/izlibstream.cpp index b361971081..9d57545360 100644 --- a/src/io/izlibstream.cpp +++ b/src/io/izlibstream.cpp @@ -27,76 +27,75 @@ namespace zlib { -inflate_streambuf::inflate_streambuf(std::istream& input, size_t bufsize, int window_bits): - zlib_streambuf(bufsize), is(input), stream_end(false) -{ - zstr.next_in = Z_NULL; - zstr.avail_in = 0; - int ret = inflateInit2(&zstr, window_bits); - if(ret != Z_OK) - throw zlib_error(zstr.msg, ret); + inflate_streambuf::inflate_streambuf(std::istream& input, size_t bufsize, + int window_bits) + : zlib_streambuf(bufsize), is(input), stream_end(false) + { + zstr.next_in = Z_NULL; + zstr.avail_in = 0; + int ret = inflateInit2(&zstr, window_bits); + if (ret != Z_OK) + throw zlib_error(zstr.msg, ret); - char* end = out.data() + out.size(); - setg(end, end, end); -} + char* end = out.data() + out.size(); + setg(end, end, end); + } -inflate_streambuf::~inflate_streambuf() noexcept -{ - inflateEnd(&zstr); -} + inflate_streambuf::~inflate_streambuf() noexcept + { + inflateEnd(&zstr); + } -inflate_streambuf::int_type inflate_streambuf::underflow() -{ - if(gptr() < egptr()) - return traits_type::to_int_type(*gptr()); + inflate_streambuf::int_type inflate_streambuf::underflow() + { + if (gptr() < egptr()) + return traits_type::to_int_type(*gptr()); - size_t have; - do - { - //Read if input buffer is empty - if(zstr.avail_in <= 0) - { - is.read(in.data(), in.size()); - if(is.bad()) - throw std::ios_base::failure("Input stream is bad"); - size_t count = is.gcount(); - if(count == 0 && !stream_end) - throw zlib_error("Unexpected end of stream", Z_DATA_ERROR); + size_t have; + do { + // Read if input buffer is empty + if (zstr.avail_in <= 0) { + is.read(in.data(), in.size()); + if (is.bad()) + throw std::ios_base::failure("Input stream is bad"); + size_t count = is.gcount(); + if (count == 0 && !stream_end) + throw zlib_error("Unexpected end of stream", Z_DATA_ERROR); - zstr.next_in = reinterpret_cast(in.data()); - zstr.avail_in = count; - } + zstr.next_in = reinterpret_cast(in.data()); + zstr.avail_in = count; + } - zstr.next_out = reinterpret_cast(out.data()); - zstr.avail_out = out.size(); + zstr.next_out = reinterpret_cast(out.data()); + zstr.avail_out = out.size(); - int ret = inflate(&zstr, Z_NO_FLUSH); - have = out.size() - zstr.avail_out; - switch(ret) - { - case Z_NEED_DICT: - case Z_DATA_ERROR: - throw zlib_error(zstr.msg, ret); + int ret = inflate(&zstr, Z_NO_FLUSH); + have = out.size() - zstr.avail_out; + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + throw zlib_error(zstr.msg, ret); - case Z_MEM_ERROR: - throw std::bad_alloc(); + case Z_MEM_ERROR: + throw std::bad_alloc(); - case Z_STREAM_END: - if(!stream_end) - { - stream_end = true; - //In case we consumed too much, we have to rewind the input stream - is.clear(); - is.seekg(-static_cast(zstr.avail_in), std::ios_base::cur); - } - if(have == 0) - return traits_type::eof(); - break; - } - } while(have == 0); + case Z_STREAM_END: + if (!stream_end) { + stream_end = true; + // In case we consumed too much, we have to rewind the + // input stream + is.clear(); + is.seekg(-static_cast(zstr.avail_in), + std::ios_base::cur); + } + if (have == 0) + return traits_type::eof(); + break; + } + } while (have == 0); - setg(out.data(), out.data(), out.data() + have); - return traits_type::to_int_type(*gptr()); -} + setg(out.data(), out.data(), out.data() + have); + return traits_type::to_int_type(*gptr()); + } -} +} // namespace zlib diff --git a/src/io/ozlibstream.cpp b/src/io/ozlibstream.cpp index eefe38a91c..c8a3e862a1 100644 --- a/src/io/ozlibstream.cpp +++ b/src/io/ozlibstream.cpp @@ -27,93 +27,88 @@ namespace zlib { -deflate_streambuf::deflate_streambuf(std::ostream& output, size_t bufsize, int level, int window_bits, int mem_level, int strategy): - zlib_streambuf(bufsize), os(output) -{ - int ret = deflateInit2(&zstr, level, Z_DEFLATED, window_bits, mem_level, strategy); - if(ret != Z_OK) - throw zlib_error(zstr.msg, ret); + deflate_streambuf::deflate_streambuf(std::ostream& output, size_t bufsize, + int level, int window_bits, + int mem_level, int strategy) + : zlib_streambuf(bufsize), os(output) + { + int ret = deflateInit2(&zstr, level, Z_DEFLATED, window_bits, mem_level, + strategy); + if (ret != Z_OK) + throw zlib_error(zstr.msg, ret); - setp(in.data(), in.data() + in.size()); -} + setp(in.data(), in.data() + in.size()); + } -deflate_streambuf::~deflate_streambuf() noexcept -{ - try - { - close(); - } - catch(...) - { - //ignore as we can't do anything about it - } - deflateEnd(&zstr); -} + deflate_streambuf::~deflate_streambuf() noexcept + { + try { + close(); + } catch (...) { + // ignore as we can't do anything about it + } + deflateEnd(&zstr); + } -void deflate_streambuf::close() -{ - deflate_chunk(Z_FINISH); -} + void deflate_streambuf::close() + { + deflate_chunk(Z_FINISH); + } -void deflate_streambuf::deflate_chunk(int flush) -{ - zstr.next_in = reinterpret_cast(pbase()); - zstr.avail_in = pptr() - pbase(); - do - { - zstr.next_out = reinterpret_cast(out.data()); - zstr.avail_out = out.size(); - int ret = deflate(&zstr, flush); - if(ret != Z_OK && ret != Z_STREAM_END) - { - os.setstate(std::ios_base::failbit); - throw zlib_error(zstr.msg, ret); - } - int have = out.size() - zstr.avail_out; - if(!os.write(out.data(), have)) - throw std::ios_base::failure("Could not write to the output stream"); - } while(zstr.avail_out == 0); - setp(in.data(), in.data() + in.size()); -} + void deflate_streambuf::deflate_chunk(int flush) + { + zstr.next_in = reinterpret_cast(pbase()); + zstr.avail_in = pptr() - pbase(); + do { + zstr.next_out = reinterpret_cast(out.data()); + zstr.avail_out = out.size(); + int ret = deflate(&zstr, flush); + if (ret != Z_OK && ret != Z_STREAM_END) { + os.setstate(std::ios_base::failbit); + throw zlib_error(zstr.msg, ret); + } + int have = out.size() - zstr.avail_out; + if (!os.write(out.data(), have)) + throw std::ios_base::failure( + "Could not write to the output stream"); + } while (zstr.avail_out == 0); + setp(in.data(), in.data() + in.size()); + } -deflate_streambuf::int_type deflate_streambuf::overflow(int_type ch) -{ - deflate_chunk(); - if(ch != traits_type::eof()) - { - *pptr() = ch; - pbump(1); - } - return ch; -} + deflate_streambuf::int_type deflate_streambuf::overflow(int_type ch) + { + deflate_chunk(); + if (ch != traits_type::eof()) { + *pptr() = ch; + pbump(1); + } + return ch; + } -int deflate_streambuf::sync() -{ - deflate_chunk(); - return 0; -} + int deflate_streambuf::sync() + { + deflate_chunk(); + return 0; + } -void ozlibstream::close() -{ - // Capture the exception mask so we can restore it if close() fails. - std::ios_base::iostate old_ex = exceptions(); - try - { - buf.close(); - return; - } - catch(...) - { - // fall through to mark the stream as bad - } + void ozlibstream::close() + { + // Capture the exception mask so we can restore it if close() fails. + std::ios_base::iostate old_ex = exceptions(); + try { + buf.close(); + return; + } catch (...) { + // fall through to mark the stream as bad + } - // Setting the stream state while exceptions are enabled may cause - // `setstate` to throw an `ios_base::failure` which would replace - // the original exception. Temporarily disable exceptions on this - // stream, set the `badbit`, then restore the exception mask. - exceptions(std::ios_base::goodbit); - setstate(std::ios_base::badbit); - exceptions(old_ex); -} + // Setting the stream state while exceptions are enabled may cause + // `setstate` to throw an `ios_base::failure` which would replace + // the original exception. Temporarily disable exceptions on this + // stream, set the `badbit`, then restore the exception mask. + exceptions(std::ios_base::goodbit); + setstate(std::ios_base::badbit); + exceptions(old_ex); + } } // namespace zlib diff --git a/src/io/stream_reader.cpp b/src/io/stream_reader.cpp index 06375ca0d5..43032b1120 100644 --- a/src/io/stream_reader.cpp +++ b/src/io/stream_reader.cpp @@ -28,92 +28,95 @@ namespace nbt { -namespace io -{ + namespace io + { -static constexpr int MAX_DEPTH = 1024; + static constexpr int MAX_DEPTH = 1024; -std::pair> read_compound(std::istream& is, endian::endian e) -{ - return stream_reader(is, e).read_compound(); -} + std::pair> + read_compound(std::istream& is, endian::endian e) + { + return stream_reader(is, e).read_compound(); + } -std::pair> read_tag(std::istream& is, endian::endian e) -{ - return stream_reader(is, e).read_tag(); -} + std::pair> read_tag(std::istream& is, + endian::endian e) + { + return stream_reader(is, e).read_tag(); + } -stream_reader::stream_reader(std::istream& is, endian::endian e) noexcept: - is(is), endian(e) -{} + stream_reader::stream_reader(std::istream& is, + endian::endian e) noexcept + : is(is), endian(e) + { + } -std::istream& stream_reader::get_istr() const -{ - return is; -} + std::istream& stream_reader::get_istr() const + { + return is; + } -endian::endian stream_reader::get_endian() const -{ - return endian; -} + endian::endian stream_reader::get_endian() const + { + return endian; + } -std::pair> stream_reader::read_compound() -{ - if(read_type() != tag_type::Compound) - { - is.setstate(std::ios::failbit); - throw input_error("Tag is not a compound"); - } - std::string key = read_string(); - auto comp = make_unique(); - comp->read_payload(*this); - return {std::move(key), std::move(comp)}; -} + std::pair> + stream_reader::read_compound() + { + if (read_type() != tag_type::Compound) { + is.setstate(std::ios::failbit); + throw input_error("Tag is not a compound"); + } + std::string key = read_string(); + auto comp = make_unique(); + comp->read_payload(*this); + return {std::move(key), std::move(comp)}; + } -std::pair> stream_reader::read_tag() -{ - tag_type type = read_type(); - std::string key = read_string(); - std::unique_ptr t = read_payload(type); - return {std::move(key), std::move(t)}; -} + std::pair> stream_reader::read_tag() + { + tag_type type = read_type(); + std::string key = read_string(); + std::unique_ptr t = read_payload(type); + return {std::move(key), std::move(t)}; + } -std::unique_ptr stream_reader::read_payload(tag_type type) -{ - if (++depth > MAX_DEPTH) - throw input_error("Too deeply nested"); - std::unique_ptr t = tag::create(type); - t->read_payload(*this); - --depth; - return t; -} + std::unique_ptr stream_reader::read_payload(tag_type type) + { + if (++depth > MAX_DEPTH) + throw input_error("Too deeply nested"); + std::unique_ptr t = tag::create(type); + t->read_payload(*this); + --depth; + return t; + } -tag_type stream_reader::read_type(bool allow_end) -{ - int type = is.get(); - if(!is) - throw input_error("Error reading tag type"); - if(!is_valid_type(type, allow_end)) - { - is.setstate(std::ios::failbit); - throw input_error("Invalid tag type: " + std::to_string(type)); - } - return static_cast(type); -} + tag_type stream_reader::read_type(bool allow_end) + { + int type = is.get(); + if (!is) + throw input_error("Error reading tag type"); + if (!is_valid_type(type, allow_end)) { + is.setstate(std::ios::failbit); + throw input_error("Invalid tag type: " + std::to_string(type)); + } + return static_cast(type); + } -std::string stream_reader::read_string() -{ - uint16_t len; - read_num(len); - if(!is) - throw input_error("Error reading string"); + std::string stream_reader::read_string() + { + uint16_t len; + read_num(len); + if (!is) + throw input_error("Error reading string"); - std::string ret(len, '\0'); - is.read(&ret[0], len); //C++11 allows us to do this - if(!is) - throw input_error("Error reading string"); - return ret; -} + std::string ret(len, '\0'); + is.read(&ret[0], len); // C++11 allows us to do this + if (!is) + throw input_error("Error reading string"); + return ret; + } -} -} + } // namespace io +} // namespace nbt diff --git a/src/io/stream_writer.cpp b/src/io/stream_writer.cpp index 0294a6d4e9..5275c5a1b4 100644 --- a/src/io/stream_writer.cpp +++ b/src/io/stream_writer.cpp @@ -26,33 +26,34 @@ namespace nbt { -namespace io -{ + namespace io + { -void write_tag(const std::string& key, const tag& t, std::ostream& os, endian::endian e) -{ - stream_writer(os, e).write_tag(key, t); -} + void write_tag(const std::string& key, const tag& t, std::ostream& os, + endian::endian e) + { + stream_writer(os, e).write_tag(key, t); + } -void stream_writer::write_tag(const std::string& key, const tag& t) -{ - write_type(t.get_type()); - write_string(key); - write_payload(t); -} + void stream_writer::write_tag(const std::string& key, const tag& t) + { + write_type(t.get_type()); + write_string(key); + write_payload(t); + } -void stream_writer::write_string(const std::string& str) -{ - if(str.size() > max_string_len) - { - os.setstate(std::ios::failbit); - std::ostringstream sstr; - sstr << "String is too long for NBT (" << str.size() << " > " << max_string_len << ")"; - throw std::length_error(sstr.str()); - } - write_num(static_cast(str.size())); - os.write(str.data(), str.size()); -} + void stream_writer::write_string(const std::string& str) + { + if (str.size() > max_string_len) { + os.setstate(std::ios::failbit); + std::ostringstream sstr; + sstr << "String is too long for NBT (" << str.size() << " > " + << max_string_len << ")"; + throw std::length_error(sstr.str()); + } + write_num(static_cast(str.size())); + os.write(str.data(), str.size()); + } -} -} + } // namespace io +} // namespace nbt diff --git a/src/tag.cpp b/src/tag.cpp index 0446dfd3f8..8e57548f2d 100644 --- a/src/tag.cpp +++ b/src/tag.cpp @@ -32,136 +32,170 @@ namespace nbt { -//Explicit instantiation definitions for tag_primitive -template class tag_primitive; -template class tag_primitive; -template class tag_primitive; -template class tag_primitive; -template class tag_primitive; -template class tag_primitive; - -static_assert(std::numeric_limits::is_iec559 && std::numeric_limits::is_iec559, - "The floating point values for NBT must conform to IEC 559/IEEE 754"); - -bool is_valid_type(int type, bool allow_end) -{ - return (allow_end ? 0 : 1) <= type && type <= 12; -} - -std::unique_ptr tag::clone() && -{ - return std::move(*this).move_clone(); -} - -namespace -{ - template - std::unique_ptr create_numeric_tag(tag_type type, T val) - { - switch(type) - { - case tag_type::Byte: return make_unique(static_cast(val)); - case tag_type::Short: return make_unique(static_cast(val)); - case tag_type::Int: return make_unique(static_cast(val)); - case tag_type::Long: return make_unique(static_cast(val)); - case tag_type::Float: return make_unique(static_cast(val)); - case tag_type::Double: return make_unique(static_cast(val)); - default: throw std::invalid_argument("Invalid numeric tag type"); - } - } -} - -std::unique_ptr tag::create(tag_type type) -{ - switch(type) - { - case tag_type::Byte: return make_unique(); - case tag_type::Short: return make_unique(); - case tag_type::Int: return make_unique(); - case tag_type::Long: return make_unique(); - case tag_type::Float: return make_unique(); - case tag_type::Double: return make_unique(); - case tag_type::Byte_Array: return make_unique(); - case tag_type::String: return make_unique(); - case tag_type::List: return make_unique(); - case tag_type::Compound: return make_unique(); - case tag_type::Int_Array: return make_unique(); - case tag_type::Long_Array: return make_unique(); - - default: throw std::invalid_argument("Invalid tag type"); - } -} - -std::unique_ptr tag::create(tag_type type, int8_t val) -{ - return create_numeric_tag(type, val); -} - -std::unique_ptr tag::create(tag_type type, int16_t val) -{ - return create_numeric_tag(type, val); -} - -std::unique_ptr tag::create(tag_type type, int32_t val) -{ - return create_numeric_tag(type, val); -} - -std::unique_ptr tag::create(tag_type type, int64_t val) -{ - return create_numeric_tag(type, val); -} - -std::unique_ptr tag::create(tag_type type, float val) -{ - return create_numeric_tag(type, val); -} - -std::unique_ptr tag::create(tag_type type, double val) -{ - return create_numeric_tag(type, val); -} - -bool operator==(const tag& lhs, const tag& rhs) -{ - if(typeid(lhs) != typeid(rhs)) - return false; - return lhs.equals(rhs); -} - -bool operator!=(const tag& lhs, const tag& rhs) -{ - return !(lhs == rhs); -} - -std::ostream& operator<<(std::ostream& os, tag_type tt) -{ - switch(tt) - { - case tag_type::End: return os << "end"; - case tag_type::Byte: return os << "byte"; - case tag_type::Short: return os << "short"; - case tag_type::Int: return os << "int"; - case tag_type::Long: return os << "long"; - case tag_type::Float: return os << "float"; - case tag_type::Double: return os << "double"; - case tag_type::Byte_Array: return os << "byte_array"; - case tag_type::String: return os << "string"; - case tag_type::List: return os << "list"; - case tag_type::Compound: return os << "compound"; - case tag_type::Int_Array: return os << "int_array"; - case tag_type::Long_Array: return os << "long_array"; - case tag_type::Null: return os << "null"; - - default: return os << "invalid"; - } -} - -std::ostream& operator<<(std::ostream& os, const tag& t) -{ - static const text::json_formatter formatter; - formatter.print(os, t); - return os; -} - -} + // Explicit instantiation definitions for tag_primitive + template class tag_primitive; + template class tag_primitive; + template class tag_primitive; + template class tag_primitive; + template class tag_primitive; + template class tag_primitive; + + static_assert( + std::numeric_limits::is_iec559 && + std::numeric_limits::is_iec559, + "The floating point values for NBT must conform to IEC 559/IEEE 754"); + + bool is_valid_type(int type, bool allow_end) + { + return (allow_end ? 0 : 1) <= type && type <= 12; + } + + std::unique_ptr tag::clone() && + { + return std::move(*this).move_clone(); + } + + namespace + { + template + std::unique_ptr create_numeric_tag(tag_type type, T val) + { + switch (type) { + case tag_type::Byte: + return make_unique(static_cast(val)); + case tag_type::Short: + return make_unique(static_cast(val)); + case tag_type::Int: + return make_unique(static_cast(val)); + case tag_type::Long: + return make_unique(static_cast(val)); + case tag_type::Float: + return make_unique(static_cast(val)); + case tag_type::Double: + return make_unique(static_cast(val)); + default: + throw std::invalid_argument("Invalid numeric tag type"); + } + } + } // namespace + + std::unique_ptr tag::create(tag_type type) + { + switch (type) { + case tag_type::Byte: + return make_unique(); + case tag_type::Short: + return make_unique(); + case tag_type::Int: + return make_unique(); + case tag_type::Long: + return make_unique(); + case tag_type::Float: + return make_unique(); + case tag_type::Double: + return make_unique(); + case tag_type::Byte_Array: + return make_unique(); + case tag_type::String: + return make_unique(); + case tag_type::List: + return make_unique(); + case tag_type::Compound: + return make_unique(); + case tag_type::Int_Array: + return make_unique(); + case tag_type::Long_Array: + return make_unique(); + + default: + throw std::invalid_argument("Invalid tag type"); + } + } + + std::unique_ptr tag::create(tag_type type, int8_t val) + { + return create_numeric_tag(type, val); + } + + std::unique_ptr tag::create(tag_type type, int16_t val) + { + return create_numeric_tag(type, val); + } + + std::unique_ptr tag::create(tag_type type, int32_t val) + { + return create_numeric_tag(type, val); + } + + std::unique_ptr tag::create(tag_type type, int64_t val) + { + return create_numeric_tag(type, val); + } + + std::unique_ptr tag::create(tag_type type, float val) + { + return create_numeric_tag(type, val); + } + + std::unique_ptr tag::create(tag_type type, double val) + { + return create_numeric_tag(type, val); + } + + bool operator==(const tag& lhs, const tag& rhs) + { + if (typeid(lhs) != typeid(rhs)) + return false; + return lhs.equals(rhs); + } + + bool operator!=(const tag& lhs, const tag& rhs) + { + return !(lhs == rhs); + } + + std::ostream& operator<<(std::ostream& os, tag_type tt) + { + switch (tt) { + case tag_type::End: + return os << "end"; + case tag_type::Byte: + return os << "byte"; + case tag_type::Short: + return os << "short"; + case tag_type::Int: + return os << "int"; + case tag_type::Long: + return os << "long"; + case tag_type::Float: + return os << "float"; + case tag_type::Double: + return os << "double"; + case tag_type::Byte_Array: + return os << "byte_array"; + case tag_type::String: + return os << "string"; + case tag_type::List: + return os << "list"; + case tag_type::Compound: + return os << "compound"; + case tag_type::Int_Array: + return os << "int_array"; + case tag_type::Long_Array: + return os << "long_array"; + case tag_type::Null: + return os << "null"; + + default: + return os << "invalid"; + } + } + + std::ostream& operator<<(std::ostream& os, const tag& t) + { + static const text::json_formatter formatter; + formatter.print(os, t); + return os; + } + +} // namespace nbt diff --git a/src/tag_compound.cpp b/src/tag_compound.cpp index c71166de95..c35ef5d53f 100644 --- a/src/tag_compound.cpp +++ b/src/tag_compound.cpp @@ -30,84 +30,80 @@ namespace nbt { -tag_compound::tag_compound(std::initializer_list> init) -{ - for(const auto& pair: init) - tags.emplace(std::move(pair.first), std::move(pair.second)); -} + tag_compound::tag_compound( + std::initializer_list> init) + { + for (const auto& pair : init) + tags.emplace(std::move(pair.first), std::move(pair.second)); + } -value& tag_compound::at(const std::string& key) -{ - return tags.at(key); -} + value& tag_compound::at(const std::string& key) + { + return tags.at(key); + } -const value& tag_compound::at(const std::string& key) const -{ - return tags.at(key); -} + const value& tag_compound::at(const std::string& key) const + { + return tags.at(key); + } -std::pair tag_compound::put(const std::string& key, value_initializer&& val) -{ - auto it = tags.find(key); - if(it != tags.end()) - { - it->second = std::move(val); - return {it, false}; - } - else - { - return tags.emplace(key, std::move(val)); - } -} + std::pair + tag_compound::put(const std::string& key, value_initializer&& val) + { + auto it = tags.find(key); + if (it != tags.end()) { + it->second = std::move(val); + return {it, false}; + } else { + return tags.emplace(key, std::move(val)); + } + } -std::pair tag_compound::insert(const std::string& key, value_initializer&& val) -{ - return tags.emplace(key, std::move(val)); -} + std::pair + tag_compound::insert(const std::string& key, value_initializer&& val) + { + return tags.emplace(key, std::move(val)); + } -bool tag_compound::erase(const std::string& key) -{ - return tags.erase(key) != 0; -} + bool tag_compound::erase(const std::string& key) + { + return tags.erase(key) != 0; + } -bool tag_compound::has_key(const std::string& key) const -{ - return tags.find(key) != tags.end(); -} + bool tag_compound::has_key(const std::string& key) const + { + return tags.find(key) != tags.end(); + } -bool tag_compound::has_key(const std::string& key, tag_type type) const -{ - auto it = tags.find(key); - return it != tags.end() && it->second.get_type() == type; -} + bool tag_compound::has_key(const std::string& key, tag_type type) const + { + auto it = tags.find(key); + return it != tags.end() && it->second.get_type() == type; + } -void tag_compound::read_payload(io::stream_reader& reader) -{ - clear(); - tag_type tt; - while((tt = reader.read_type(true)) != tag_type::End) - { - std::string key; - try - { - key = reader.read_string(); - } - catch(io::input_error& ex) - { - std::ostringstream str; - str << "Error reading key of tag_" << tt; - throw io::input_error(str.str()); - } - auto tptr = reader.read_payload(tt); - tags.emplace(std::move(key), value(std::move(tptr))); - } -} + void tag_compound::read_payload(io::stream_reader& reader) + { + clear(); + tag_type tt; + while ((tt = reader.read_type(true)) != tag_type::End) { + std::string key; + try { + key = reader.read_string(); + } catch (io::input_error& ex) { + std::ostringstream str; + str << "Error reading key of tag_" << tt; + throw io::input_error(str.str()); + } + auto tptr = reader.read_payload(tt); + tags.emplace(std::move(key), value(std::move(tptr))); + } + } -void tag_compound::write_payload(io::stream_writer& writer) const -{ - for(const auto& pair: tags) - writer.write_tag(pair.first, pair.second); - writer.write_type(tag_type::End); -} + void tag_compound::write_payload(io::stream_writer& writer) const + { + for (const auto& pair : tags) + writer.write_tag(pair.first, pair.second); + writer.write_type(tag_type::End); + } -} +} // namespace nbt diff --git a/src/tag_list.cpp b/src/tag_list.cpp index f72f86bb24..413af779ae 100644 --- a/src/tag_list.cpp +++ b/src/tag_list.cpp @@ -30,126 +30,158 @@ namespace nbt { -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } -tag_list::tag_list(std::initializer_list il) { init(il); } - -tag_list::tag_list(std::initializer_list init) -{ - if(init.size() == 0) - el_type_ = tag_type::Null; - else - { - el_type_ = init.begin()->get_type(); - for(const value& val: init) - { - if(!val || val.get_type() != el_type_) - throw std::invalid_argument("The values are not all the same type"); - } - tags.assign(init.begin(), init.end()); - } -} - -value& tag_list::at(size_t i) -{ - return tags.at(i); -} - -const value& tag_list::at(size_t i) const -{ - return tags.at(i); -} - -void tag_list::set(size_t i, value&& val) -{ - if(val.get_type() != el_type_) - throw std::invalid_argument("The tag type does not match the list's content type"); - tags.at(i) = std::move(val); -} - -void tag_list::push_back(value_initializer&& val) -{ - if(!val) //don't allow null values - throw std::invalid_argument("The value must not be null"); - if(el_type_ == tag_type::Null) //set content type if undetermined - el_type_ = val.get_type(); - else if(el_type_ != val.get_type()) - throw std::invalid_argument("The tag type does not match the list's content type"); - tags.push_back(std::move(val)); -} - -void tag_list::reset(tag_type type) -{ - clear(); - el_type_ = type; -} - -void tag_list::read_payload(io::stream_reader& reader) -{ - tag_type lt = reader.read_type(true); - - int32_t length; - reader.read_num(length); - if(length < 0) - reader.get_istr().setstate(std::ios::failbit); - if(!reader.get_istr()) - throw io::input_error("Error reading length of tag_list"); - - if(lt != tag_type::End) - { - reset(lt); - tags.reserve(length); - - for(int32_t i = 0; i < length; ++i) - tags.emplace_back(reader.read_payload(lt)); - } - else - { - //In case of tag_end, ignore the length and leave the type undetermined - reset(tag_type::Null); - } -} - -void tag_list::write_payload(io::stream_writer& writer) const -{ - if(size() > io::stream_writer::max_array_len) - { - writer.get_ostr().setstate(std::ios::failbit); - throw std::length_error("List is too large for NBT"); - } - writer.write_type(el_type_ != tag_type::Null - ? el_type_ - : tag_type::End); - writer.write_num(static_cast(size())); - for(const auto& val: tags) - { - //check if the value is of the correct type - if(val.get_type() != el_type_) - { - writer.get_ostr().setstate(std::ios::failbit); - throw std::logic_error("The tags in the list do not all match the content type"); - } - writer.write_payload(val); - } -} - -bool operator==(const tag_list& lhs, const tag_list& rhs) -{ - return lhs.el_type_ == rhs.el_type_ && lhs.tags == rhs.tags; -} - -bool operator!=(const tag_list& lhs, const tag_list& rhs) -{ - return !(lhs == rhs); -} - -} + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + tag_list::tag_list(std::initializer_list il) + { + init(il); + } + + tag_list::tag_list(std::initializer_list init) + { + if (init.size() == 0) + el_type_ = tag_type::Null; + else { + el_type_ = init.begin()->get_type(); + for (const value& val : init) { + if (!val || val.get_type() != el_type_) + throw std::invalid_argument( + "The values are not all the same type"); + } + tags.assign(init.begin(), init.end()); + } + } + + value& tag_list::at(size_t i) + { + return tags.at(i); + } + + const value& tag_list::at(size_t i) const + { + return tags.at(i); + } + + void tag_list::set(size_t i, value&& val) + { + if (val.get_type() != el_type_) + throw std::invalid_argument( + "The tag type does not match the list's content type"); + tags.at(i) = std::move(val); + } + + void tag_list::push_back(value_initializer&& val) + { + if (!val) // don't allow null values + throw std::invalid_argument("The value must not be null"); + if (el_type_ == tag_type::Null) // set content type if undetermined + el_type_ = val.get_type(); + else if (el_type_ != val.get_type()) + throw std::invalid_argument( + "The tag type does not match the list's content type"); + tags.push_back(std::move(val)); + } + + void tag_list::reset(tag_type type) + { + clear(); + el_type_ = type; + } + + void tag_list::read_payload(io::stream_reader& reader) + { + tag_type lt = reader.read_type(true); + + int32_t length; + reader.read_num(length); + if (length < 0) + reader.get_istr().setstate(std::ios::failbit); + if (!reader.get_istr()) + throw io::input_error("Error reading length of tag_list"); + + if (lt != tag_type::End) { + reset(lt); + tags.reserve(length); + + for (int32_t i = 0; i < length; ++i) + tags.emplace_back(reader.read_payload(lt)); + } else { + // In case of tag_end, ignore the length and leave the type + // undetermined + reset(tag_type::Null); + } + } + + void tag_list::write_payload(io::stream_writer& writer) const + { + if (size() > io::stream_writer::max_array_len) { + writer.get_ostr().setstate(std::ios::failbit); + throw std::length_error("List is too large for NBT"); + } + writer.write_type(el_type_ != tag_type::Null ? el_type_ + : tag_type::End); + writer.write_num(static_cast(size())); + for (const auto& val : tags) { + // check if the value is of the correct type + if (val.get_type() != el_type_) { + writer.get_ostr().setstate(std::ios::failbit); + throw std::logic_error( + "The tags in the list do not all match the content type"); + } + writer.write_payload(val); + } + } + + bool operator==(const tag_list& lhs, const tag_list& rhs) + { + return lhs.el_type_ == rhs.el_type_ && lhs.tags == rhs.tags; + } + + bool operator!=(const tag_list& lhs, const tag_list& rhs) + { + return !(lhs == rhs); + } + +} // namespace nbt diff --git a/src/tag_string.cpp b/src/tag_string.cpp index e630b1dfc4..481ad19dee 100644 --- a/src/tag_string.cpp +++ b/src/tag_string.cpp @@ -28,21 +28,18 @@ namespace nbt { -void tag_string::read_payload(io::stream_reader& reader) -{ - try - { - value = reader.read_string(); - } - catch(io::input_error& ex) - { - throw io::input_error("Error reading tag_string"); - } -} + void tag_string::read_payload(io::stream_reader& reader) + { + try { + value = reader.read_string(); + } catch (io::input_error& ex) { + throw io::input_error("Error reading tag_string"); + } + } -void tag_string::write_payload(io::stream_writer& writer) const -{ - writer.write_string(value); -} + void tag_string::write_payload(io::stream_writer& writer) const + { + writer.write_string(value); + } -} +} // namespace nbt diff --git a/src/text/json_formatter.cpp b/src/text/json_formatter.cpp index 88f3a7c58e..46d726a3c9 100644 --- a/src/text/json_formatter.cpp +++ b/src/text/json_formatter.cpp @@ -30,209 +30,227 @@ namespace nbt { -namespace text -{ + namespace text + { -namespace //anonymous -{ - ///Helper class which uses the Visitor pattern to pretty-print tags - class json_fmt_visitor : public const_nbt_visitor - { - public: - json_fmt_visitor(std::ostream& os): - os(os) - {} - - void visit(const tag_byte& b) override - { os << static_cast(b.get()) << "b"; } //We don't want to print a character - - void visit(const tag_short& s) override - { os << s.get() << "s"; } - - void visit(const tag_int& i) override - { os << i.get(); } - - void visit(const tag_long& l) override - { os << l.get() << "l"; } - - void visit(const tag_float& f) override - { - write_float(f.get()); - os << "f"; - } - - void visit(const tag_double& d) override - { - write_float(d.get()); - os << "d"; - } - - void visit(const tag_byte_array& ba) override - { os << "[" << ba.size() << " bytes]"; } - - void visit(const tag_string& s) override - { - os << '"'; - write_escaped_string(s.get()); - os << '"'; - } - - void visit(const tag_list& l) override - { - //Wrap lines for lists of lists or compounds. - //Lists of other types can usually be on one line without problem. - const bool break_lines = l.size() > 0 && - (l.el_type() == tag_type::List || l.el_type() == tag_type::Compound); - - os << "["; - if(break_lines) - { - os << "\n"; - ++indent_lvl; - for(unsigned int i = 0; i < l.size(); ++i) - { - indent(); - if(l[i]) - l[i].get().accept(*this); - else - write_null(); - if(i != l.size()-1) - os << ","; - os << "\n"; - } - --indent_lvl; - indent(); - } - else - { - for(unsigned int i = 0; i < l.size(); ++i) - { - if(l[i]) - l[i].get().accept(*this); - else - write_null(); - if(i != l.size()-1) - os << ", "; - } - } - os << "]"; - } - - void visit(const tag_compound& c) override - { - if(c.size() == 0) //No line breaks inside empty compounds please - { - os << "{}"; - return; - } - - os << "{\n"; - ++indent_lvl; - unsigned int i = 0; - for(const auto& kv: c) - { - indent(); - os << kv.first << ": "; - if(kv.second) - kv.second.get().accept(*this); - else - write_null(); - if(i != c.size()-1) - os << ","; - os << "\n"; - ++i; - } - --indent_lvl; - indent(); - os << "}"; - } - - void visit(const tag_int_array& ia) override - { - os << "["; - for(unsigned int i = 0; i < ia.size(); ++i) - { - os << ia[i]; - if(i != ia.size()-1) - os << ", "; - } - os << "]"; - } - - void visit(const tag_long_array& la) override - { - os << "["; - for(unsigned int i = 0; i < la.size(); ++i) - { - os << la[i]; - if(i != la.size()-1) - os << ", "; - } - os << "]"; - } - - private: - const std::string indent_str = " "; - - std::ostream& os; - int indent_lvl = 0; - - void indent() - { - for(int i = 0; i < indent_lvl; ++i) - os << indent_str; - } - - template - void write_float(T val, int precision = std::numeric_limits::max_digits10) - { - if(std::isfinite(val)) - os << std::setprecision(precision) << val; - else if(std::isinf(val)) - { - if(std::signbit(val)) - os << "-"; - os << "Infinity"; - } - else - os << "NaN"; - } - - void write_null() - { - os << "null"; - } - - void write_escaped_string(const std::string& str) - { - for (char c : str) { - switch (c) { - case '"': os << "\\\""; break; - case '\\': os << "\\\\"; break; - case '\b': os << "\\b"; break; - case '\f': os << "\\f"; break; - case '\n': os << "\\n"; break; - case '\r': os << "\\r"; break; - case '\t': os << "\\t"; break; - default: - if (c < 32 || c == 127) { - // Control characters, escape as \u00XX - os << "\\u00" << std::hex << std::setw(2) << std::setfill('0') << static_cast(c); - } else { - os << c; - } - break; - } - } - } - }; -} - -void json_formatter::print(std::ostream& os, const tag& t) const -{ - json_fmt_visitor v(os); - t.accept(v); -} + namespace // anonymous + { + /// Helper class which uses the Visitor pattern to pretty-print tags + class json_fmt_visitor : public const_nbt_visitor + { + public: + json_fmt_visitor(std::ostream& os) : os(os) {} + + void visit(const tag_byte& b) override + { + os << static_cast(b.get()) << "b"; + } // We don't want to print a character + + void visit(const tag_short& s) override + { + os << s.get() << "s"; + } + + void visit(const tag_int& i) override + { + os << i.get(); + } + + void visit(const tag_long& l) override + { + os << l.get() << "l"; + } + + void visit(const tag_float& f) override + { + write_float(f.get()); + os << "f"; + } + + void visit(const tag_double& d) override + { + write_float(d.get()); + os << "d"; + } + + void visit(const tag_byte_array& ba) override + { + os << "[" << ba.size() << " bytes]"; + } + + void visit(const tag_string& s) override + { + os << '"'; + write_escaped_string(s.get()); + os << '"'; + } + + void visit(const tag_list& l) override + { + // Wrap lines for lists of lists or compounds. + // Lists of other types can usually be on one line without + // problem. + const bool break_lines = + l.size() > 0 && (l.el_type() == tag_type::List || + l.el_type() == tag_type::Compound); + + os << "["; + if (break_lines) { + os << "\n"; + ++indent_lvl; + for (unsigned int i = 0; i < l.size(); ++i) { + indent(); + if (l[i]) + l[i].get().accept(*this); + else + write_null(); + if (i != l.size() - 1) + os << ","; + os << "\n"; + } + --indent_lvl; + indent(); + } else { + for (unsigned int i = 0; i < l.size(); ++i) { + if (l[i]) + l[i].get().accept(*this); + else + write_null(); + if (i != l.size() - 1) + os << ", "; + } + } + os << "]"; + } + + void visit(const tag_compound& c) override + { + if (c.size() == + 0) // No line breaks inside empty compounds please + { + os << "{}"; + return; + } + + os << "{\n"; + ++indent_lvl; + unsigned int i = 0; + for (const auto& kv : c) { + indent(); + os << kv.first << ": "; + if (kv.second) + kv.second.get().accept(*this); + else + write_null(); + if (i != c.size() - 1) + os << ","; + os << "\n"; + ++i; + } + --indent_lvl; + indent(); + os << "}"; + } + + void visit(const tag_int_array& ia) override + { + os << "["; + for (unsigned int i = 0; i < ia.size(); ++i) { + os << ia[i]; + if (i != ia.size() - 1) + os << ", "; + } + os << "]"; + } + + void visit(const tag_long_array& la) override + { + os << "["; + for (unsigned int i = 0; i < la.size(); ++i) { + os << la[i]; + if (i != la.size() - 1) + os << ", "; + } + os << "]"; + } + + private: + const std::string indent_str = " "; + + std::ostream& os; + int indent_lvl = 0; + + void indent() + { + for (int i = 0; i < indent_lvl; ++i) + os << indent_str; + } + + template + void write_float( + T val, int precision = std::numeric_limits::max_digits10) + { + if (std::isfinite(val)) + os << std::setprecision(precision) << val; + else if (std::isinf(val)) { + if (std::signbit(val)) + os << "-"; + os << "Infinity"; + } else + os << "NaN"; + } + + void write_null() + { + os << "null"; + } + + void write_escaped_string(const std::string& str) + { + for (char c : str) { + switch (c) { + case '"': + os << "\\\""; + break; + case '\\': + os << "\\\\"; + break; + case '\b': + os << "\\b"; + break; + case '\f': + os << "\\f"; + break; + case '\n': + os << "\\n"; + break; + case '\r': + os << "\\r"; + break; + case '\t': + os << "\\t"; + break; + default: + if (c < 32 || c == 127) { + // Control characters, escape as \u00XX + os << "\\u00" << std::hex << std::setw(2) + << std::setfill('0') + << static_cast(c); + } else { + os << c; + } + break; + } + } + } + }; + } // namespace + + void json_formatter::print(std::ostream& os, const tag& t) const + { + json_fmt_visitor v(os); + t.accept(v); + } -} -} + } // namespace text +} // namespace nbt diff --git a/src/value.cpp b/src/value.cpp index bf0ffcad78..3a9ccb71bc 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -32,286 +32,296 @@ namespace nbt { -value::value(tag&& t): - tag_(std::move(t).move_clone()) -{} - -value::value(const value& rhs): - tag_(rhs.tag_ ? rhs.tag_->clone() : nullptr) -{} - -value& value::operator=(const value& rhs) -{ - if(this != &rhs) - { - tag_ = rhs.tag_ ? rhs.tag_->clone() : nullptr; - } - return *this; -} - -value& value::operator=(tag&& t) -{ - set(std::move(t)); - return *this; -} - -void value::set(tag&& t) -{ - if(tag_) - tag_->assign(std::move(t)); - else - tag_ = std::move(t).move_clone(); -} - -//Primitive assignment -namespace // helper functions local to this translation unit -{ - template - void assign_numeric_impl(std::unique_ptr& tag_ptr, T val, - tag_type default_type) - { - using nbt::tag_type; - if(!tag_ptr) - { - tag_ptr = tag::create(default_type, val); - return; - } - - // Determine the incoming tag type for T - auto incoming_type = detail::get_primitive_type::value; - - // If the existing tag is of a narrower type than the incoming type, - // reject the assignment to avoid widening the stored tag type. - auto existing_type = tag_ptr->get_type(); - - if(static_cast(existing_type) < static_cast(incoming_type)) - { - throw std::bad_cast(); - } - - // Existing type is same or wider: write into the existing tag (may narrow) - switch(existing_type) - { - case tag_type::Byte: static_cast(*tag_ptr).set(static_cast(val)); break; - case tag_type::Short: static_cast(*tag_ptr).set(static_cast(val)); break; - case tag_type::Int: static_cast(*tag_ptr).set(static_cast(val)); break; - case tag_type::Long: static_cast(*tag_ptr).set(static_cast(val)); break; - case tag_type::Float: static_cast(*tag_ptr).set(static_cast(val)); break; - case tag_type::Double: static_cast(*tag_ptr).set(static_cast(val)); break; - default: throw std::bad_cast(); - } - } -} - -value& value::operator=(int8_t val) -{ - assign_numeric_impl(tag_, val, tag_type::Byte); - return *this; -} - -value& value::operator=(int16_t val) -{ - assign_numeric_impl(tag_, val, tag_type::Short); - return *this; -} - -value& value::operator=(int32_t val) -{ - assign_numeric_impl(tag_, val, tag_type::Int); - return *this; -} - -value& value::operator=(int64_t val) -{ - assign_numeric_impl(tag_, val, tag_type::Long); - return *this; -} - -value& value::operator=(float val) -{ - assign_numeric_impl(tag_, val, tag_type::Float); - return *this; -} - -value& value::operator=(double val) -{ - assign_numeric_impl(tag_, val, tag_type::Double); - return *this; -} - -//Primitive conversion -value::operator int8_t() const -{ - switch(tag_->get_type()) - { - case tag_type::Byte: - return static_cast(*tag_).get(); - - default: - throw std::bad_cast(); - } -} - -value::operator int16_t() const -{ - switch(tag_->get_type()) - { - case tag_type::Byte: - return static_cast(*tag_).get(); - case tag_type::Short: - return static_cast(*tag_).get(); - - default: - throw std::bad_cast(); - } -} - -value::operator int32_t() const -{ - switch(tag_->get_type()) - { - case tag_type::Byte: - return static_cast(*tag_).get(); - case tag_type::Short: - return static_cast(*tag_).get(); - case tag_type::Int: - return static_cast(*tag_).get(); - - default: - throw std::bad_cast(); - } -} - -value::operator int64_t() const -{ - switch(tag_->get_type()) - { - case tag_type::Byte: - return static_cast(*tag_).get(); - case tag_type::Short: - return static_cast(*tag_).get(); - case tag_type::Int: - return static_cast(*tag_).get(); - case tag_type::Long: - return static_cast(*tag_).get(); - - default: - throw std::bad_cast(); - } -} - -value::operator float() const -{ - switch(tag_->get_type()) - { - case tag_type::Byte: - return static_cast(*tag_).get(); - case tag_type::Short: - return static_cast(*tag_).get(); - case tag_type::Int: - return static_cast(*tag_).get(); - case tag_type::Long: - return static_cast(*tag_).get(); - case tag_type::Float: - return static_cast(*tag_).get(); - - default: - throw std::bad_cast(); - } -} - -value::operator double() const -{ - switch(tag_->get_type()) - { - case tag_type::Byte: - return static_cast(*tag_).get(); - case tag_type::Short: - return static_cast(*tag_).get(); - case tag_type::Int: - return static_cast(*tag_).get(); - case tag_type::Long: - return static_cast(*tag_).get(); - case tag_type::Float: - return static_cast(*tag_).get(); - case tag_type::Double: - return static_cast(*tag_).get(); - - default: - throw std::bad_cast(); - } -} - -value& value::operator=(std::string&& str) -{ - if(!tag_) - set(tag_string(std::move(str))); - else - dynamic_cast(*tag_).set(std::move(str)); - return *this; -} - -value::operator const std::string&() const -{ - return dynamic_cast(*tag_).get(); -} - -value& value::at(const std::string& key) -{ - return dynamic_cast(*tag_).at(key); -} - -const value& value::at(const std::string& key) const -{ - return dynamic_cast(*tag_).at(key); -} - -value& value::operator[](const std::string& key) -{ - return dynamic_cast(*tag_)[key]; -} - -value& value::operator[](const char* key) -{ - return (*this)[std::string(key)]; -} - -value& value::at(size_t i) -{ - return dynamic_cast(*tag_).at(i); -} - -const value& value::at(size_t i) const -{ - return dynamic_cast(*tag_).at(i); -} - -value& value::operator[](size_t i) -{ - return dynamic_cast(*tag_)[i]; -} - -const value& value::operator[](size_t i) const -{ - return dynamic_cast(*tag_)[i]; -} - -tag_type value::get_type() const -{ - return tag_ ? tag_->get_type() : tag_type::Null; -} - -bool operator==(const value& lhs, const value& rhs) -{ - if(lhs.tag_ != nullptr && rhs.tag_ != nullptr) - return *lhs.tag_ == *rhs.tag_; - else - return lhs.tag_ == nullptr && rhs.tag_ == nullptr; -} - -bool operator!=(const value& lhs, const value& rhs) -{ - return !(lhs == rhs); -} - -} + value::value(tag&& t) : tag_(std::move(t).move_clone()) {} + + value::value(const value& rhs) + : tag_(rhs.tag_ ? rhs.tag_->clone() : nullptr) + { + } + + value& value::operator=(const value& rhs) + { + if (this != &rhs) { + tag_ = rhs.tag_ ? rhs.tag_->clone() : nullptr; + } + return *this; + } + + value& value::operator=(tag&& t) + { + set(std::move(t)); + return *this; + } + + void value::set(tag&& t) + { + if (tag_) + tag_->assign(std::move(t)); + else + tag_ = std::move(t).move_clone(); + } + + // Primitive assignment + namespace // helper functions local to this translation unit + { + template + void assign_numeric_impl(std::unique_ptr& tag_ptr, T val, + tag_type default_type) + { + using nbt::tag_type; + if (!tag_ptr) { + tag_ptr = tag::create(default_type, val); + return; + } + + // Determine the incoming tag type for T + auto incoming_type = detail::get_primitive_type::value; + + // If the existing tag is of a narrower type than the incoming type, + // reject the assignment to avoid widening the stored tag type. + auto existing_type = tag_ptr->get_type(); + + if (static_cast(existing_type) < + static_cast(incoming_type)) { + throw std::bad_cast(); + } + + // Existing type is same or wider: write into the existing tag (may + // narrow) + switch (existing_type) { + case tag_type::Byte: + static_cast(*tag_ptr).set( + static_cast(val)); + break; + case tag_type::Short: + static_cast(*tag_ptr).set( + static_cast(val)); + break; + case tag_type::Int: + static_cast(*tag_ptr).set( + static_cast(val)); + break; + case tag_type::Long: + static_cast(*tag_ptr).set( + static_cast(val)); + break; + case tag_type::Float: + static_cast(*tag_ptr).set( + static_cast(val)); + break; + case tag_type::Double: + static_cast(*tag_ptr).set( + static_cast(val)); + break; + default: + throw std::bad_cast(); + } + } + } // namespace + + value& value::operator=(int8_t val) + { + assign_numeric_impl(tag_, val, tag_type::Byte); + return *this; + } + + value& value::operator=(int16_t val) + { + assign_numeric_impl(tag_, val, tag_type::Short); + return *this; + } + + value& value::operator=(int32_t val) + { + assign_numeric_impl(tag_, val, tag_type::Int); + return *this; + } + + value& value::operator=(int64_t val) + { + assign_numeric_impl(tag_, val, tag_type::Long); + return *this; + } + + value& value::operator=(float val) + { + assign_numeric_impl(tag_, val, tag_type::Float); + return *this; + } + + value& value::operator=(double val) + { + assign_numeric_impl(tag_, val, tag_type::Double); + return *this; + } + + // Primitive conversion + value::operator int8_t() const + { + switch (tag_->get_type()) { + case tag_type::Byte: + return static_cast(*tag_).get(); + + default: + throw std::bad_cast(); + } + } + + value::operator int16_t() const + { + switch (tag_->get_type()) { + case tag_type::Byte: + return static_cast(*tag_).get(); + case tag_type::Short: + return static_cast(*tag_).get(); + + default: + throw std::bad_cast(); + } + } + + value::operator int32_t() const + { + switch (tag_->get_type()) { + case tag_type::Byte: + return static_cast(*tag_).get(); + case tag_type::Short: + return static_cast(*tag_).get(); + case tag_type::Int: + return static_cast(*tag_).get(); + + default: + throw std::bad_cast(); + } + } + + value::operator int64_t() const + { + switch (tag_->get_type()) { + case tag_type::Byte: + return static_cast(*tag_).get(); + case tag_type::Short: + return static_cast(*tag_).get(); + case tag_type::Int: + return static_cast(*tag_).get(); + case tag_type::Long: + return static_cast(*tag_).get(); + + default: + throw std::bad_cast(); + } + } + + value::operator float() const + { + switch (tag_->get_type()) { + case tag_type::Byte: + return static_cast(*tag_).get(); + case tag_type::Short: + return static_cast(*tag_).get(); + case tag_type::Int: + return static_cast(*tag_).get(); + case tag_type::Long: + return static_cast(*tag_).get(); + case tag_type::Float: + return static_cast(*tag_).get(); + + default: + throw std::bad_cast(); + } + } + + value::operator double() const + { + switch (tag_->get_type()) { + case tag_type::Byte: + return static_cast(*tag_).get(); + case tag_type::Short: + return static_cast(*tag_).get(); + case tag_type::Int: + return static_cast(*tag_).get(); + case tag_type::Long: + return static_cast(*tag_).get(); + case tag_type::Float: + return static_cast(*tag_).get(); + case tag_type::Double: + return static_cast(*tag_).get(); + + default: + throw std::bad_cast(); + } + } + + value& value::operator=(std::string&& str) + { + if (!tag_) + set(tag_string(std::move(str))); + else + dynamic_cast(*tag_).set(std::move(str)); + return *this; + } + + value::operator const std::string&() const + { + return dynamic_cast(*tag_).get(); + } + + value& value::at(const std::string& key) + { + return dynamic_cast(*tag_).at(key); + } + + const value& value::at(const std::string& key) const + { + return dynamic_cast(*tag_).at(key); + } + + value& value::operator[](const std::string& key) + { + return dynamic_cast(*tag_)[key]; + } + + value& value::operator[](const char* key) + { + return (*this)[std::string(key)]; + } + + value& value::at(size_t i) + { + return dynamic_cast(*tag_).at(i); + } + + const value& value::at(size_t i) const + { + return dynamic_cast(*tag_).at(i); + } + + value& value::operator[](size_t i) + { + return dynamic_cast(*tag_)[i]; + } + + const value& value::operator[](size_t i) const + { + return dynamic_cast(*tag_)[i]; + } + + tag_type value::get_type() const + { + return tag_ ? tag_->get_type() : tag_type::Null; + } + + bool operator==(const value& lhs, const value& rhs) + { + if (lhs.tag_ != nullptr && rhs.tag_ != nullptr) + return *lhs.tag_ == *rhs.tag_; + else + return lhs.tag_ == nullptr && rhs.tag_ == nullptr; + } + + bool operator!=(const value& lhs, const value& rhs) + { + return !(lhs == rhs); + } + +} // namespace nbt diff --git a/src/value_initializer.cpp b/src/value_initializer.cpp index b0d0d3d747..ec52f544fe 100644 --- a/src/value_initializer.cpp +++ b/src/value_initializer.cpp @@ -27,14 +27,23 @@ namespace nbt { -value_initializer::value_initializer(int8_t val) : value(tag_byte(val)) {} -value_initializer::value_initializer(int16_t val) : value(tag_short(val)) {} -value_initializer::value_initializer(int32_t val) : value(tag_int(val)) {} -value_initializer::value_initializer(int64_t val) : value(tag_long(val)) {} -value_initializer::value_initializer(float val) : value(tag_float(val)) {} -value_initializer::value_initializer(double val) : value(tag_double(val)) {} -value_initializer::value_initializer(const std::string& str): value(tag_string(str)) {} -value_initializer::value_initializer(std::string&& str) : value(tag_string(std::move(str))) {} -value_initializer::value_initializer(const char* str) : value(tag_string(str)) {} + value_initializer::value_initializer(int8_t val) : value(tag_byte(val)) {} + value_initializer::value_initializer(int16_t val) : value(tag_short(val)) {} + value_initializer::value_initializer(int32_t val) : value(tag_int(val)) {} + value_initializer::value_initializer(int64_t val) : value(tag_long(val)) {} + value_initializer::value_initializer(float val) : value(tag_float(val)) {} + value_initializer::value_initializer(double val) : value(tag_double(val)) {} + value_initializer::value_initializer(const std::string& str) + : value(tag_string(str)) + { + } + value_initializer::value_initializer(std::string&& str) + : value(tag_string(std::move(str))) + { + } + value_initializer::value_initializer(const char* str) + : value(tag_string(str)) + { + } -} +} // namespace nbt diff --git a/test/endian_str_test.h b/test/endian_str_test.h index 386ba60b57..d647d563eb 100644 --- a/test/endian_str_test.h +++ b/test/endian_str_test.h @@ -33,149 +33,137 @@ using namespace endian; class endian_str_test : public CxxTest::TestSuite { -public: - void test_uint() - { - std::stringstream str(std::ios::in | std::ios::out | std::ios::binary); - - write_little(str, uint8_t (0x01)); - write_little(str, uint16_t(0x0102)); - write (str, uint32_t(0x01020304), little); - write_little(str, uint64_t(0x0102030405060708)); - - write_big (str, uint8_t (0x09)); - write_big (str, uint16_t(0x090A)); - write_big (str, uint32_t(0x090A0B0C)); - write (str, uint64_t(0x090A0B0C0D0E0F10), big); - - std::string expected{ - 1, - 2, 1, - 4, 3, 2, 1, - 8, 7, 6, 5, 4, 3, 2, 1, - - 9, - 9, 10, - 9, 10, 11, 12, - 9, 10, 11, 12, 13, 14, 15, 16 - }; - TS_ASSERT_EQUALS(str.str(), expected); - - uint8_t u8; - uint16_t u16; - uint32_t u32; - uint64_t u64; - - read_little(str, u8); - TS_ASSERT_EQUALS(u8, 0x01); - read_little(str, u16); - TS_ASSERT_EQUALS(u16, 0x0102); - read_little(str, u32); - TS_ASSERT_EQUALS(u32, 0x01020304u); - read(str, u64, little); - TS_ASSERT_EQUALS(u64, 0x0102030405060708u); - - read_big(str, u8); - TS_ASSERT_EQUALS(u8, 0x09); - read_big(str, u16); - TS_ASSERT_EQUALS(u16, 0x090A); - read(str, u32, big); - TS_ASSERT_EQUALS(u32, 0x090A0B0Cu); - read_big(str, u64); - TS_ASSERT_EQUALS(u64, 0x090A0B0C0D0E0F10u); - - TS_ASSERT(str); //Check if stream has failed - } - - void test_sint() - { - std::stringstream str(std::ios::in | std::ios::out | std::ios::binary); - - write_little(str, int8_t (-0x01)); - write_little(str, int16_t(-0x0102)); - write_little(str, int32_t(-0x01020304)); - write (str, int64_t(-0x0102030405060708), little); - - write_big (str, int8_t (-0x09)); - write_big (str, int16_t(-0x090A)); - write (str, int32_t(-0x090A0B0C), big); - write_big (str, int64_t(-0x090A0B0C0D0E0F10)); - - std::string expected{ //meh, stupid narrowing conversions - '\xFF', - '\xFE', '\xFE', - '\xFC', '\xFC', '\xFD', '\xFE', - '\xF8', '\xF8', '\xF9', '\xFA', '\xFB', '\xFC', '\xFD', '\xFE', - - '\xF7', - '\xF6', '\xF6', - '\xF6', '\xF5', '\xF4', '\xF4', - '\xF6', '\xF5', '\xF4', '\xF3', '\xF2', '\xF1', '\xF0', '\xF0' - }; - TS_ASSERT_EQUALS(str.str(), expected); - - int8_t i8; - int16_t i16; - int32_t i32; - int64_t i64; - - read_little(str, i8); - TS_ASSERT_EQUALS(i8, -0x01); - read_little(str, i16); - TS_ASSERT_EQUALS(i16, -0x0102); - read(str, i32, little); - TS_ASSERT_EQUALS(i32, -0x01020304); - read_little(str, i64); - TS_ASSERT_EQUALS(i64, -0x0102030405060708); - - read_big(str, i8); - TS_ASSERT_EQUALS(i8, -0x09); - read_big(str, i16); - TS_ASSERT_EQUALS(i16, -0x090A); - read_big(str, i32); - TS_ASSERT_EQUALS(i32, -0x090A0B0C); - read(str, i64, big); - TS_ASSERT_EQUALS(i64, -0x090A0B0C0D0E0F10); - - TS_ASSERT(str); //Check if stream has failed - } - - void test_float() - { - std::stringstream str(std::ios::in | std::ios::out | std::ios::binary); - - //C99 has hexadecimal floating point literals, C++ doesn't... - const float fconst = std::stof("-0xCDEF01p-63"); //-1.46325e-012 - const double dconst = std::stod("-0x1DEF0102030405p-375"); //-1.09484e-097 - //We will be assuming IEEE 754 here - - write_little(str, fconst); - write_little(str, dconst); - write_big (str, fconst); - write_big (str, dconst); - - std::string expected{ - '\x01', '\xEF', '\xCD', '\xAB', - '\x05', '\x04', '\x03', '\x02', '\x01', '\xEF', '\xCD', '\xAB', - - '\xAB', '\xCD', '\xEF', '\x01', - '\xAB', '\xCD', '\xEF', '\x01', '\x02', '\x03', '\x04', '\x05' - }; - TS_ASSERT_EQUALS(str.str(), expected); - - float f; - double d; - - read_little(str, f); - TS_ASSERT_EQUALS(f, fconst); - read_little(str, d); - TS_ASSERT_EQUALS(d, dconst); - - read_big(str, f); - TS_ASSERT_EQUALS(f, fconst); - read_big(str, d); - TS_ASSERT_EQUALS(d, dconst); - - TS_ASSERT(str); //Check if stream has failed - } + public: + void test_uint() + { + std::stringstream str(std::ios::in | std::ios::out | std::ios::binary); + + write_little(str, uint8_t(0x01)); + write_little(str, uint16_t(0x0102)); + write(str, uint32_t(0x01020304), little); + write_little(str, uint64_t(0x0102030405060708)); + + write_big(str, uint8_t(0x09)); + write_big(str, uint16_t(0x090A)); + write_big(str, uint32_t(0x090A0B0C)); + write(str, uint64_t(0x090A0B0C0D0E0F10), big); + + std::string expected{ + 1, 2, 1, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, + + 9, 9, 10, 9, 10, 11, 12, 9, 10, 11, 12, 13, 14, 15, 16}; + TS_ASSERT_EQUALS(str.str(), expected); + + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + + read_little(str, u8); + TS_ASSERT_EQUALS(u8, 0x01); + read_little(str, u16); + TS_ASSERT_EQUALS(u16, 0x0102); + read_little(str, u32); + TS_ASSERT_EQUALS(u32, 0x01020304u); + read(str, u64, little); + TS_ASSERT_EQUALS(u64, 0x0102030405060708u); + + read_big(str, u8); + TS_ASSERT_EQUALS(u8, 0x09); + read_big(str, u16); + TS_ASSERT_EQUALS(u16, 0x090A); + read(str, u32, big); + TS_ASSERT_EQUALS(u32, 0x090A0B0Cu); + read_big(str, u64); + TS_ASSERT_EQUALS(u64, 0x090A0B0C0D0E0F10u); + + TS_ASSERT(str); // Check if stream has failed + } + + void test_sint() + { + std::stringstream str(std::ios::in | std::ios::out | std::ios::binary); + + write_little(str, int8_t(-0x01)); + write_little(str, int16_t(-0x0102)); + write_little(str, int32_t(-0x01020304)); + write(str, int64_t(-0x0102030405060708), little); + + write_big(str, int8_t(-0x09)); + write_big(str, int16_t(-0x090A)); + write(str, int32_t(-0x090A0B0C), big); + write_big(str, int64_t(-0x090A0B0C0D0E0F10)); + + std::string expected{ + // meh, stupid narrowing conversions + '\xFF', '\xFE', '\xFE', '\xFC', '\xFC', '\xFD', '\xFE', '\xF8', + '\xF8', '\xF9', '\xFA', '\xFB', '\xFC', '\xFD', '\xFE', + + '\xF7', '\xF6', '\xF6', '\xF6', '\xF5', '\xF4', '\xF4', '\xF6', + '\xF5', '\xF4', '\xF3', '\xF2', '\xF1', '\xF0', '\xF0'}; + TS_ASSERT_EQUALS(str.str(), expected); + + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + + read_little(str, i8); + TS_ASSERT_EQUALS(i8, -0x01); + read_little(str, i16); + TS_ASSERT_EQUALS(i16, -0x0102); + read(str, i32, little); + TS_ASSERT_EQUALS(i32, -0x01020304); + read_little(str, i64); + TS_ASSERT_EQUALS(i64, -0x0102030405060708); + + read_big(str, i8); + TS_ASSERT_EQUALS(i8, -0x09); + read_big(str, i16); + TS_ASSERT_EQUALS(i16, -0x090A); + read_big(str, i32); + TS_ASSERT_EQUALS(i32, -0x090A0B0C); + read(str, i64, big); + TS_ASSERT_EQUALS(i64, -0x090A0B0C0D0E0F10); + + TS_ASSERT(str); // Check if stream has failed + } + + void test_float() + { + std::stringstream str(std::ios::in | std::ios::out | std::ios::binary); + + // C99 has hexadecimal floating point literals, C++ doesn't... + const float fconst = std::stof("-0xCDEF01p-63"); //-1.46325e-012 + const double dconst = + std::stod("-0x1DEF0102030405p-375"); //-1.09484e-097 + // We will be assuming IEEE 754 here + + write_little(str, fconst); + write_little(str, dconst); + write_big(str, fconst); + write_big(str, dconst); + + std::string expected{'\x01', '\xEF', '\xCD', '\xAB', '\x05', '\x04', + '\x03', '\x02', '\x01', '\xEF', '\xCD', '\xAB', + + '\xAB', '\xCD', '\xEF', '\x01', '\xAB', '\xCD', + '\xEF', '\x01', '\x02', '\x03', '\x04', '\x05'}; + TS_ASSERT_EQUALS(str.str(), expected); + + float f; + double d; + + read_little(str, f); + TS_ASSERT_EQUALS(f, fconst); + read_little(str, d); + TS_ASSERT_EQUALS(d, dconst); + + read_big(str, f); + TS_ASSERT_EQUALS(f, fconst); + read_big(str, d); + TS_ASSERT_EQUALS(d, dconst); + + TS_ASSERT(str); // Check if stream has failed + } }; diff --git a/test/format_test.cpp b/test/format_test.cpp index ed8d2d1bfc..f7d95985c9 100644 --- a/test/format_test.cpp +++ b/test/format_test.cpp @@ -21,7 +21,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with libnbt++. If not, see . */ -//#include "text/json_formatter.h" +// #include "text/json_formatter.h" #include "io/stream_reader.h" #include "io/stream_writer.h" #include @@ -33,73 +33,74 @@ using namespace nbt; int main() { - // Write that into a file and read back for testing - tag_compound comp{ - {"byte", tag_byte(-128)}, - {"short", tag_short(-32768)}, - {"int", tag_int(-2147483648)}, - {"long", tag_long(-9223372036854775808U)}, + // Write that into a file and read back for testing + tag_compound comp{ + {"byte", tag_byte(-128)}, + {"short", tag_short(-32768)}, + {"int", tag_int(-2147483648)}, + {"long", tag_long(-9223372036854775808U)}, - {"float 1", 1.618034f}, - {"float 2", 6.626070e-34f}, - {"float 3", 2.273737e+29f}, - {"float 4", -std::numeric_limits::infinity()}, - {"float 5", std::numeric_limits::quiet_NaN()}, + {"float 1", 1.618034f}, + {"float 2", 6.626070e-34f}, + {"float 3", 2.273737e+29f}, + {"float 4", -std::numeric_limits::infinity()}, + {"float 5", std::numeric_limits::quiet_NaN()}, - {"double 1", 3.141592653589793}, - {"double 2", 1.749899444387479e-193}, - {"double 3", 2.850825855152578e+175}, - {"double 4", -std::numeric_limits::infinity()}, - {"double 5", std::numeric_limits::quiet_NaN()}, + {"double 1", 3.141592653589793}, + {"double 2", 1.749899444387479e-193}, + {"double 3", 2.850825855152578e+175}, + {"double 4", -std::numeric_limits::infinity()}, + {"double 5", std::numeric_limits::quiet_NaN()}, - {"string 1", "Hello World! \u00E4\u00F6\u00FC\u00DF"}, - {"string 2", "String with\nline breaks\tand tabs"}, + {"string 1", "Hello World! \u00E4\u00F6\u00FC\u00DF"}, + {"string 2", "String with\nline breaks\tand tabs"}, - {"byte array", tag_byte_array{12, 13, 14, 15, 16}}, - {"int array", tag_int_array{0x0badc0de, -0x0dedbeef, 0x1badbabe}}, - {"long array", tag_long_array{0x0badc0de0badc0de, -0x0dedbeef0dedbeef, 0x1badbabe1badbabe}}, + {"byte array", tag_byte_array{12, 13, 14, 15, 16}}, + {"int array", tag_int_array{0x0badc0de, -0x0dedbeef, 0x1badbabe}}, + {"long array", tag_long_array{0x0badc0de0badc0de, -0x0dedbeef0dedbeef, + 0x1badbabe1badbabe}}, - {"list (empty)", tag_list::of({})}, - {"list (float)", tag_list{2.0f, 1.0f, 0.5f, 0.25f}}, - {"list (list)", tag_list::of({ - {}, - {4, 5, 6}, - {tag_compound{{"egg", "ham"}}, tag_compound{{"foo", "bar"}}} - })}, - {"list (compound)", tag_list::of({ - {{"created-on", 42}, {"names", tag_list{"Compound", "tag", "#0"}}}, - {{"created-on", 45}, {"names", tag_list{"Compound", "tag", "#1"}}} - })}, + {"list (empty)", tag_list::of({})}, + {"list (float)", tag_list{2.0f, 1.0f, 0.5f, 0.25f}}, + {"list (list)", + tag_list::of( + {{}, + {4, 5, 6}, + {tag_compound{{"egg", "ham"}}, tag_compound{{"foo", "bar"}}}})}, + {"list (compound)", + tag_list::of( + {{{"created-on", 42}, + {"names", tag_list{"Compound", "tag", "#0"}}}, + {{"created-on", 45}, + {"names", tag_list{"Compound", "tag", "#1"}}}})}, - {"compound (empty)", tag_compound()}, - {"compound (nested)", tag_compound{ - {"key", "value"}, - {"key with \u00E4\u00F6\u00FC", tag_byte(-1)}, - {"key with\nnewline and\ttab", tag_compound{}} - }}, + {"compound (empty)", tag_compound()}, + {"compound (nested)", + tag_compound{{"key", "value"}, + {"key with \u00E4\u00F6\u00FC", tag_byte(-1)}, + {"key with\nnewline and\ttab", tag_compound{}}}}, - {"null", nullptr} - }; + {"null", nullptr}}; - std::cout << "----- default operator<<:\n"; - std::cout << comp; - std::cout << "\n-----" << std::endl; + std::cout << "----- default operator<<:\n"; + std::cout << comp; + std::cout << "\n-----" << std::endl; - // Write to file and read back - { - tag_compound file_comp = comp; - file_comp.erase("null"); - std::ofstream out("test_output.nbt", std::ios::binary); - nbt::io::write_tag("root", file_comp, out); - } + // Write to file and read back + { + tag_compound file_comp = comp; + file_comp.erase("null"); + std::ofstream out("test_output.nbt", std::ios::binary); + nbt::io::write_tag("root", file_comp, out); + } - { - std::ifstream in("test_output.nbt", std::ios::binary); - auto read_pair = nbt::io::read_compound(in); - std::cout << "----- read back from file:\n"; - std::cout << *read_pair.second; - std::cout << "\n-----" << std::endl; - } + { + std::ifstream in("test_output.nbt", std::ios::binary); + auto read_pair = nbt::io::read_compound(in); + std::cout << "----- read back from file:\n"; + std::cout << *read_pair.second; + std::cout << "\n-----" << std::endl; + } - return 0; + return 0; } diff --git a/test/nbttest.h b/test/nbttest.h index 4d7f2cb5de..ce660e355d 100644 --- a/test/nbttest.h +++ b/test/nbttest.h @@ -34,471 +34,539 @@ using namespace nbt; class nbttest : public CxxTest::TestSuite { -public: - void test_tag() - { - TS_ASSERT(!is_valid_type(-1)); - TS_ASSERT(!is_valid_type(0)); - TS_ASSERT(is_valid_type(0, true)); - TS_ASSERT(is_valid_type(1)); - TS_ASSERT(is_valid_type(5, false)); - TS_ASSERT(is_valid_type(7, true)); - TS_ASSERT(is_valid_type(12)); - TS_ASSERT(!is_valid_type(13)); - - //looks like TS_ASSERT_EQUALS can't handle abstract classes... - TS_ASSERT(*tag::create(tag_type::Byte) == tag_byte()); - TS_ASSERT_THROWS(tag::create(tag_type::Null), std::invalid_argument); - TS_ASSERT_THROWS(tag::create(tag_type::End), std::invalid_argument); - - tag_string tstr("foo"); - auto cl = tstr.clone(); - TS_ASSERT_EQUALS(tstr.get(), "foo"); - TS_ASSERT(tstr == *cl); - - cl = std::move(tstr).clone(); - TS_ASSERT(*cl == tag_string("foo")); - TS_ASSERT(*cl != tag_string("bar")); - - cl = std::move(*cl).move_clone(); - TS_ASSERT(*cl == tag_string("foo")); - - tstr.assign(tag_string("bar")); - TS_ASSERT_THROWS(tstr.assign(tag_int(6)), std::bad_cast); - TS_ASSERT_EQUALS(tstr.get(), "bar"); - - TS_ASSERT_EQUALS(&tstr.as(), &tstr); - TS_ASSERT_THROWS(tstr.as(), std::bad_cast); - } - - void test_get_type() - { - TS_ASSERT_EQUALS(tag_byte().get_type() , tag_type::Byte); - TS_ASSERT_EQUALS(tag_short().get_type() , tag_type::Short); - TS_ASSERT_EQUALS(tag_int().get_type() , tag_type::Int); - TS_ASSERT_EQUALS(tag_long().get_type() , tag_type::Long); - TS_ASSERT_EQUALS(tag_float().get_type() , tag_type::Float); - TS_ASSERT_EQUALS(tag_double().get_type() , tag_type::Double); - TS_ASSERT_EQUALS(tag_byte_array().get_type(), tag_type::Byte_Array); - TS_ASSERT_EQUALS(tag_string().get_type() , tag_type::String); - TS_ASSERT_EQUALS(tag_list().get_type() , tag_type::List); - TS_ASSERT_EQUALS(tag_compound().get_type() , tag_type::Compound); - TS_ASSERT_EQUALS(tag_int_array().get_type() , tag_type::Int_Array); - TS_ASSERT_EQUALS(tag_long_array().get_type(), tag_type::Long_Array); - } - - void test_tag_primitive() - { - tag_int tag(6); - TS_ASSERT_EQUALS(tag.get(), 6); - int& ref = tag; - ref = 12; - TS_ASSERT(tag == 12); - TS_ASSERT(tag != 6); - tag.set(24); - TS_ASSERT_EQUALS(ref, 24); - tag = 7; - TS_ASSERT_EQUALS(static_cast(tag), 7); - - TS_ASSERT_EQUALS(tag, tag_int(7)); - TS_ASSERT_DIFFERS(tag_float(2.5), tag_float(-2.5)); - TS_ASSERT_DIFFERS(tag_float(2.5), tag_double(2.5)); - - TS_ASSERT(tag_double() == 0.0); - - TS_ASSERT_EQUALS(tag_byte(INT8_MAX).get(), INT8_MAX); - TS_ASSERT_EQUALS(tag_byte(INT8_MIN).get(), INT8_MIN); - TS_ASSERT_EQUALS(tag_short(INT16_MAX).get(), INT16_MAX); - TS_ASSERT_EQUALS(tag_short(INT16_MIN).get(), INT16_MIN); - TS_ASSERT_EQUALS(tag_int(INT32_MAX).get(), INT32_MAX); - TS_ASSERT_EQUALS(tag_int(INT32_MIN).get(), INT32_MIN); - TS_ASSERT_EQUALS(tag_long(INT64_MAX).get(), INT64_MAX); - TS_ASSERT_EQUALS(tag_long(INT64_MIN).get(), INT64_MIN); - } - - void test_tag_string() - { - tag_string tag("foo"); - TS_ASSERT_EQUALS(tag.get(), "foo"); - std::string& ref = tag; - ref = "bar"; - TS_ASSERT_EQUALS(tag.get(), "bar"); - TS_ASSERT_DIFFERS(tag.get(), "foo"); - tag.set("baz"); - TS_ASSERT_EQUALS(ref, "baz"); - tag = "quux"; - TS_ASSERT_EQUALS("quux", static_cast(tag)); - std::string str("foo"); - tag = str; - TS_ASSERT_EQUALS(tag.get(),str); - - TS_ASSERT_EQUALS(tag_string(str).get(), "foo"); - TS_ASSERT_EQUALS(tag_string().get(), ""); - } - - void test_tag_compound() - { - tag_compound comp{ - {"foo", int16_t(12)}, - {"bar", "baz"}, - {"baz", -2.0}, - {"list", tag_list{16, 17}} - }; - - //Test assignments and conversions, and exceptions on bad conversions - TS_ASSERT_EQUALS(comp["foo"].get_type(), tag_type::Short); - TS_ASSERT_EQUALS(static_cast(comp["foo"]), 12); - TS_ASSERT_EQUALS(static_cast(comp.at("foo")), int16_t(12)); - TS_ASSERT(comp["foo"] == tag_short(12)); - TS_ASSERT_THROWS(static_cast(comp["foo"]), std::bad_cast); - TS_ASSERT_THROWS(static_cast(comp["foo"]), std::bad_cast); - - TS_ASSERT_THROWS(comp["foo"] = 32, std::bad_cast); - comp["foo"] = int8_t(32); - TS_ASSERT_EQUALS(static_cast(comp["foo"]), 32); - - TS_ASSERT_EQUALS(comp["bar"].get_type(), tag_type::String); - TS_ASSERT_EQUALS(static_cast(comp["bar"]), "baz"); - TS_ASSERT_THROWS(static_cast(comp["bar"]), std::bad_cast); - - TS_ASSERT_THROWS(comp["bar"] = -128, std::bad_cast); - comp["bar"] = "barbaz"; - TS_ASSERT_EQUALS(static_cast(comp["bar"]), "barbaz"); - - TS_ASSERT_EQUALS(comp["baz"].get_type(), tag_type::Double); - TS_ASSERT_EQUALS(static_cast(comp["baz"]), -2.0); - TS_ASSERT_THROWS(static_cast(comp["baz"]), std::bad_cast); - - //Test nested access - comp["quux"] = tag_compound{{"Hello", "World"}, {"zero", 0}}; - TS_ASSERT_EQUALS(comp.at("quux").get_type(), tag_type::Compound); - TS_ASSERT_EQUALS(static_cast(comp["quux"].at("Hello")), "World"); - TS_ASSERT_EQUALS(static_cast(comp["quux"]["Hello"]), "World"); - TS_ASSERT(comp["list"][1] == tag_int(17)); - - TS_ASSERT_THROWS(comp.at("nothing"), std::out_of_range); - - //Test equality comparisons - tag_compound comp2{ - {"foo", int16_t(32)}, - {"bar", "barbaz"}, - {"baz", -2.0}, - {"quux", tag_compound{{"Hello", "World"}, {"zero", 0}}}, - {"list", tag_list{16, 17}} - }; - TS_ASSERT(comp == comp2); - TS_ASSERT(comp != dynamic_cast(comp2["quux"].get())); - TS_ASSERT(comp != comp2["quux"]); - TS_ASSERT(dynamic_cast(comp["quux"].get()) == comp2["quux"]); - - //Test whether begin() through end() goes through all the keys and their - //values. The order of iteration is irrelevant there. - std::set keys{"bar", "baz", "foo", "list", "quux"}; - TS_ASSERT_EQUALS(comp2.size(), keys.size()); - unsigned int i = 0; - for(const std::pair& val: comp2) - { - TS_ASSERT_LESS_THAN(i, comp2.size()); - TS_ASSERT(keys.count(val.first)); - TS_ASSERT(val.second == comp2[val.first]); - ++i; - } - TS_ASSERT_EQUALS(i, comp2.size()); - - //Test erasing and has_key - TS_ASSERT_EQUALS(comp.erase("nothing"), false); - TS_ASSERT(comp.has_key("quux")); - TS_ASSERT(comp.has_key("quux", tag_type::Compound)); - TS_ASSERT(!comp.has_key("quux", tag_type::List)); - TS_ASSERT(!comp.has_key("quux", tag_type::Null)); - - TS_ASSERT_EQUALS(comp.erase("quux"), true); - TS_ASSERT(!comp.has_key("quux")); - TS_ASSERT(!comp.has_key("quux", tag_type::Compound)); - TS_ASSERT(!comp.has_key("quux", tag_type::Null)); - - comp.clear(); - TS_ASSERT(comp == tag_compound{}); - - //Test inserting values - TS_ASSERT_EQUALS(comp.put("abc", tag_double(6.0)).second, true); - TS_ASSERT_EQUALS(comp.put("abc", tag_long(-28)).second, false); - TS_ASSERT_EQUALS(comp.insert("ghi", tag_string("world")).second, true); - TS_ASSERT_EQUALS(comp.insert("abc", tag_string("hello")).second, false); - TS_ASSERT_EQUALS(comp.emplace("def", "ghi").second, true); - TS_ASSERT_EQUALS(comp.emplace("def", 4).second, false); - TS_ASSERT((comp == tag_compound{ - {"abc", tag_long(-28)}, - {"def", tag_byte(4)}, - {"ghi", tag_string("world")} - })); - } - - void test_value() - { - value val1; - value val2(make_unique(42)); - value val3(tag_int(42)); - - TS_ASSERT(!val1 && val2 && val3); - TS_ASSERT(val1 == val1); - TS_ASSERT(val1 != val2); - TS_ASSERT(val2 == val3); - TS_ASSERT(val3 == val3); - - value valstr(tag_string("foo")); - TS_ASSERT_EQUALS(static_cast(valstr), "foo"); - valstr = "bar"; - TS_ASSERT_THROWS(valstr = 5, std::bad_cast); - TS_ASSERT_EQUALS(static_cast(valstr), "bar"); - TS_ASSERT(valstr.as() == "bar"); - TS_ASSERT_EQUALS(&valstr.as(), &valstr.get()); - TS_ASSERT_THROWS(valstr.as(), std::bad_cast); - - val1 = int64_t(42); - TS_ASSERT(val2 != val1); - - TS_ASSERT_THROWS(val2 = int64_t(12), std::bad_cast); - TS_ASSERT_EQUALS(static_cast(val2), 42); - tag_int* ptr = dynamic_cast(val2.get_ptr().get()); - TS_ASSERT(*ptr == 42); - val2 = 52; - TS_ASSERT_EQUALS(static_cast(val2), 52); - TS_ASSERT(*ptr == 52); - - TS_ASSERT_THROWS(val1["foo"], std::bad_cast); - TS_ASSERT_THROWS(val1.at("foo"), std::bad_cast); - - val3 = 52; - TS_ASSERT(val2 == val3); - TS_ASSERT(val2.get_ptr() != val3.get_ptr()); - - val3 = std::move(val2); - TS_ASSERT(val3 == tag_int(52)); - TS_ASSERT(!val2); - - tag_int& tag = dynamic_cast(val3.get()); - TS_ASSERT(tag == tag_int(52)); - tag = 21; - TS_ASSERT_EQUALS(static_cast(val3), 21); - val1.set_ptr(std::move(val3.get_ptr())); - TS_ASSERT(val1.as() == 21); - - TS_ASSERT_EQUALS(val1.get_type(), tag_type::Int); - TS_ASSERT_EQUALS(val2.get_type(), tag_type::Null); - TS_ASSERT_EQUALS(val3.get_type(), tag_type::Null); - - val2 = val1; - val1 = val3; - TS_ASSERT(!val1 && val2 && !val3); - TS_ASSERT(val1.get_ptr() == nullptr); - TS_ASSERT(val2.get() == tag_int(21)); - TS_ASSERT(value(val1) == val1); - TS_ASSERT(value(val2) == val2); - val1 = val1; - val2 = val2; - TS_ASSERT(!val1); - TS_ASSERT(val1 == value_initializer(nullptr)); - TS_ASSERT(val2 == tag_int(21)); - - val3 = tag_short(2); - TS_ASSERT_THROWS(val3 = tag_string("foo"), std::bad_cast); - TS_ASSERT(val3.get() == tag_short(2)); - - val2.set_ptr(make_unique("foo")); - TS_ASSERT(val2 == tag_string("foo")); - } - - void test_tag_list() - { - tag_list list; - TS_ASSERT_EQUALS(list.el_type(), tag_type::Null); - TS_ASSERT_THROWS(list.push_back(value(nullptr)), std::invalid_argument); - - list.emplace_back("foo"); - TS_ASSERT_EQUALS(list.el_type(), tag_type::String); - list.push_back(tag_string("bar")); - TS_ASSERT_THROWS(list.push_back(tag_int(42)), std::invalid_argument); - TS_ASSERT_THROWS(list.emplace_back(), std::invalid_argument); - - TS_ASSERT((list == tag_list{"foo", "bar"})); - TS_ASSERT(list[0] == tag_string("foo")); - TS_ASSERT_EQUALS(static_cast(list.at(1)), "bar"); - - TS_ASSERT_EQUALS(list.size(), 2u); - TS_ASSERT_THROWS(list.at(2), std::out_of_range); - TS_ASSERT_THROWS(list.at(-1), std::out_of_range); - - list.set(1, value(tag_string("baz"))); - TS_ASSERT_THROWS(list.set(1, value(nullptr)), std::invalid_argument); - TS_ASSERT_THROWS(list.set(1, value(tag_int(-42))), std::invalid_argument); - TS_ASSERT_EQUALS(static_cast(list[1]), "baz"); - - TS_ASSERT_EQUALS(list.size(), 2u); - tag_string values[] = {"foo", "baz"}; - TS_ASSERT_EQUALS(list.end() - list.begin(), int(list.size())); - TS_ASSERT(std::equal(list.begin(), list.end(), values)); - - list.pop_back(); - TS_ASSERT(list == tag_list{"foo"}); - TS_ASSERT(list == tag_list::of({"foo"})); - TS_ASSERT(tag_list::of({"foo"}) == tag_list{"foo"}); - TS_ASSERT((list != tag_list{2, 3, 5, 7})); - - list.clear(); - TS_ASSERT_EQUALS(list.size(), 0u); - TS_ASSERT_EQUALS(list.el_type(), tag_type::String) - TS_ASSERT_THROWS(list.push_back(tag_short(25)), std::invalid_argument); - TS_ASSERT_THROWS(list.push_back(value(nullptr)), std::invalid_argument); - - list.reset(); - TS_ASSERT_EQUALS(list.el_type(), tag_type::Null); - list.emplace_back(17); - TS_ASSERT_EQUALS(list.el_type(), tag_type::Int); - - list.reset(tag_type::Float); - TS_ASSERT_EQUALS(list.el_type(), tag_type::Float); - list.emplace_back(17.0f); - TS_ASSERT(list == tag_list({17.0f})); - - TS_ASSERT(tag_list() != tag_list(tag_type::Int)); - TS_ASSERT(tag_list() == tag_list()); - TS_ASSERT(tag_list(tag_type::Short) != tag_list(tag_type::Int)); - TS_ASSERT(tag_list(tag_type::Short) == tag_list(tag_type::Short)); - - tag_list short_list = tag_list::of({25, 36}); - TS_ASSERT_EQUALS(short_list.el_type(), tag_type::Short); - TS_ASSERT((short_list == tag_list{int16_t(25), int16_t(36)})); - TS_ASSERT((short_list != tag_list{25, 36})); - TS_ASSERT((short_list == tag_list{value(tag_short(25)), value(tag_short(36))})); - - TS_ASSERT_THROWS((tag_list{value(tag_byte(4)), value(tag_int(5))}), std::invalid_argument); - TS_ASSERT_THROWS((tag_list{value(nullptr), value(tag_int(6))}), std::invalid_argument); - TS_ASSERT_THROWS((tag_list{value(tag_int(7)), value(tag_int(8)), value(nullptr)}), std::invalid_argument); - TS_ASSERT_EQUALS((tag_list(std::initializer_list{})).el_type(), tag_type::Null); - TS_ASSERT_EQUALS((tag_list{2, 3, 5, 7}).el_type(), tag_type::Int); - } - - void test_tag_byte_array() - { - std::vector vec{1, 2, 127, -128}; - tag_byte_array arr{1, 2, 127, -128}; - TS_ASSERT_EQUALS(arr.size(), 4u); - TS_ASSERT(arr.at(0) == 1 && arr[1] == 2 && arr[2] == 127 && arr.at(3) == -128); - TS_ASSERT_THROWS(arr.at(-1), std::out_of_range); - TS_ASSERT_THROWS(arr.at(4), std::out_of_range); - - TS_ASSERT(arr.get() == vec); - TS_ASSERT(arr == tag_byte_array(std::vector(vec))); - - arr.push_back(42); - vec.push_back(42); - - TS_ASSERT_EQUALS(arr.size(), 5u); - TS_ASSERT_EQUALS(arr.end() - arr.begin(), int(arr.size())); - TS_ASSERT(std::equal(arr.begin(), arr.end(), vec.begin())); - - arr.pop_back(); - arr.pop_back(); - TS_ASSERT_EQUALS(arr.size(), 3u); - TS_ASSERT((arr == tag_byte_array{1, 2, 127})); - TS_ASSERT((arr != tag_int_array{1, 2, 127})); - TS_ASSERT((arr != tag_long_array{1, 2, 127})); - TS_ASSERT((arr != tag_byte_array{1, 2, -1})); - - arr.clear(); - TS_ASSERT(arr == tag_byte_array()); - } - - void test_tag_int_array() - { - std::vector vec{100, 200, INT32_MAX, INT32_MIN}; - tag_int_array arr{100, 200, INT32_MAX, INT32_MIN}; - TS_ASSERT_EQUALS(arr.size(), 4u); - TS_ASSERT(arr.at(0) == 100 && arr[1] == 200 && arr[2] == INT32_MAX && arr.at(3) == INT32_MIN); - TS_ASSERT_THROWS(arr.at(-1), std::out_of_range); - TS_ASSERT_THROWS(arr.at(4), std::out_of_range); - - TS_ASSERT(arr.get() == vec); - TS_ASSERT(arr == tag_int_array(std::vector(vec))); - - arr.push_back(42); - vec.push_back(42); - - TS_ASSERT_EQUALS(arr.size(), 5u); - TS_ASSERT_EQUALS(arr.end() - arr.begin(), int(arr.size())); - TS_ASSERT(std::equal(arr.begin(), arr.end(), vec.begin())); - - arr.pop_back(); - arr.pop_back(); - TS_ASSERT_EQUALS(arr.size(), 3u); - TS_ASSERT((arr == tag_int_array{100, 200, INT32_MAX})); - TS_ASSERT((arr != tag_int_array{100, -56, -1})); - - arr.clear(); - TS_ASSERT(arr == tag_int_array()); - } - - void test_tag_long_array() - { - std::vector vec{100, 200, INT64_MAX, INT64_MIN}; - tag_long_array arr{100, 200, INT64_MAX, INT64_MIN}; - TS_ASSERT_EQUALS(arr.size(), 4u); - TS_ASSERT(arr.at(0) == 100 && arr[1] == 200 && arr[2] == INT64_MAX && arr.at(3) == INT64_MIN); - TS_ASSERT_THROWS(arr.at(-1), std::out_of_range); - TS_ASSERT_THROWS(arr.at(4), std::out_of_range); - - TS_ASSERT(arr.get() == vec); - TS_ASSERT(arr == tag_long_array(std::vector(vec))); - - arr.push_back(42); - vec.push_back(42); - - TS_ASSERT_EQUALS(arr.size(), 5u); - TS_ASSERT_EQUALS(arr.end() - arr.begin(), int(arr.size())); - TS_ASSERT(std::equal(arr.begin(), arr.end(), vec.begin())); - - arr.pop_back(); - arr.pop_back(); - TS_ASSERT_EQUALS(arr.size(), 3u); - TS_ASSERT((arr == tag_long_array{100, 200, INT64_MAX})); - TS_ASSERT((arr != tag_long_array{100, -56, -1})); - - arr.clear(); - TS_ASSERT(arr == tag_long_array()); - } - - void test_visitor() - { - struct : public nbt_visitor - { - tag* visited = nullptr; - - void visit(tag_byte& tag) { visited = &tag; } - void visit(tag_short& tag) { visited = &tag; } - void visit(tag_int& tag) { visited = &tag; } - void visit(tag_long& tag) { visited = &tag; } - void visit(tag_float& tag) { visited = &tag; } - void visit(tag_double& tag) { visited = &tag; } - void visit(tag_byte_array& tag) { visited = &tag; } - void visit(tag_string& tag) { visited = &tag; } - void visit(tag_list& tag) { visited = &tag; } - void visit(tag_compound& tag) { visited = &tag; } - void visit(tag_int_array& tag) { visited = &tag; } - void visit(tag_long_array& tag) { visited = &tag; } - } v; - - tag_byte b; b.accept(v); TS_ASSERT_EQUALS(v.visited, &b); - tag_short s; s.accept(v); TS_ASSERT_EQUALS(v.visited, &s); - tag_int i; i.accept(v); TS_ASSERT_EQUALS(v.visited, &i); - tag_long l; l.accept(v); TS_ASSERT_EQUALS(v.visited, &l); - tag_float f; f.accept(v); TS_ASSERT_EQUALS(v.visited, &f); - tag_double d; d.accept(v); TS_ASSERT_EQUALS(v.visited, &d); - tag_byte_array ba; ba.accept(v); TS_ASSERT_EQUALS(v.visited, &ba); - tag_string st; st.accept(v); TS_ASSERT_EQUALS(v.visited, &st); - tag_list ls; ls.accept(v); TS_ASSERT_EQUALS(v.visited, &ls); - tag_compound c; c.accept(v); TS_ASSERT_EQUALS(v.visited, &c); - tag_int_array ia; ia.accept(v); TS_ASSERT_EQUALS(v.visited, &ia); - tag_long_array la; la.accept(v); TS_ASSERT_EQUALS(v.visited, &la); - } + public: + void test_tag() + { + TS_ASSERT(!is_valid_type(-1)); + TS_ASSERT(!is_valid_type(0)); + TS_ASSERT(is_valid_type(0, true)); + TS_ASSERT(is_valid_type(1)); + TS_ASSERT(is_valid_type(5, false)); + TS_ASSERT(is_valid_type(7, true)); + TS_ASSERT(is_valid_type(12)); + TS_ASSERT(!is_valid_type(13)); + + // looks like TS_ASSERT_EQUALS can't handle abstract classes... + TS_ASSERT(*tag::create(tag_type::Byte) == tag_byte()); + TS_ASSERT_THROWS(tag::create(tag_type::Null), std::invalid_argument); + TS_ASSERT_THROWS(tag::create(tag_type::End), std::invalid_argument); + + tag_string tstr("foo"); + auto cl = tstr.clone(); + TS_ASSERT_EQUALS(tstr.get(), "foo"); + TS_ASSERT(tstr == *cl); + + cl = std::move(tstr).clone(); + TS_ASSERT(*cl == tag_string("foo")); + TS_ASSERT(*cl != tag_string("bar")); + + cl = std::move(*cl).move_clone(); + TS_ASSERT(*cl == tag_string("foo")); + + tstr.assign(tag_string("bar")); + TS_ASSERT_THROWS(tstr.assign(tag_int(6)), std::bad_cast); + TS_ASSERT_EQUALS(tstr.get(), "bar"); + + TS_ASSERT_EQUALS(&tstr.as(), &tstr); + TS_ASSERT_THROWS(tstr.as(), std::bad_cast); + } + + void test_get_type() + { + TS_ASSERT_EQUALS(tag_byte().get_type(), tag_type::Byte); + TS_ASSERT_EQUALS(tag_short().get_type(), tag_type::Short); + TS_ASSERT_EQUALS(tag_int().get_type(), tag_type::Int); + TS_ASSERT_EQUALS(tag_long().get_type(), tag_type::Long); + TS_ASSERT_EQUALS(tag_float().get_type(), tag_type::Float); + TS_ASSERT_EQUALS(tag_double().get_type(), tag_type::Double); + TS_ASSERT_EQUALS(tag_byte_array().get_type(), tag_type::Byte_Array); + TS_ASSERT_EQUALS(tag_string().get_type(), tag_type::String); + TS_ASSERT_EQUALS(tag_list().get_type(), tag_type::List); + TS_ASSERT_EQUALS(tag_compound().get_type(), tag_type::Compound); + TS_ASSERT_EQUALS(tag_int_array().get_type(), tag_type::Int_Array); + TS_ASSERT_EQUALS(tag_long_array().get_type(), tag_type::Long_Array); + } + + void test_tag_primitive() + { + tag_int tag(6); + TS_ASSERT_EQUALS(tag.get(), 6); + int& ref = tag; + ref = 12; + TS_ASSERT(tag == 12); + TS_ASSERT(tag != 6); + tag.set(24); + TS_ASSERT_EQUALS(ref, 24); + tag = 7; + TS_ASSERT_EQUALS(static_cast(tag), 7); + + TS_ASSERT_EQUALS(tag, tag_int(7)); + TS_ASSERT_DIFFERS(tag_float(2.5), tag_float(-2.5)); + TS_ASSERT_DIFFERS(tag_float(2.5), tag_double(2.5)); + + TS_ASSERT(tag_double() == 0.0); + + TS_ASSERT_EQUALS(tag_byte(INT8_MAX).get(), INT8_MAX); + TS_ASSERT_EQUALS(tag_byte(INT8_MIN).get(), INT8_MIN); + TS_ASSERT_EQUALS(tag_short(INT16_MAX).get(), INT16_MAX); + TS_ASSERT_EQUALS(tag_short(INT16_MIN).get(), INT16_MIN); + TS_ASSERT_EQUALS(tag_int(INT32_MAX).get(), INT32_MAX); + TS_ASSERT_EQUALS(tag_int(INT32_MIN).get(), INT32_MIN); + TS_ASSERT_EQUALS(tag_long(INT64_MAX).get(), INT64_MAX); + TS_ASSERT_EQUALS(tag_long(INT64_MIN).get(), INT64_MIN); + } + + void test_tag_string() + { + tag_string tag("foo"); + TS_ASSERT_EQUALS(tag.get(), "foo"); + std::string& ref = tag; + ref = "bar"; + TS_ASSERT_EQUALS(tag.get(), "bar"); + TS_ASSERT_DIFFERS(tag.get(), "foo"); + tag.set("baz"); + TS_ASSERT_EQUALS(ref, "baz"); + tag = "quux"; + TS_ASSERT_EQUALS("quux", static_cast(tag)); + std::string str("foo"); + tag = str; + TS_ASSERT_EQUALS(tag.get(), str); + + TS_ASSERT_EQUALS(tag_string(str).get(), "foo"); + TS_ASSERT_EQUALS(tag_string().get(), ""); + } + + void test_tag_compound() + { + tag_compound comp{{"foo", int16_t(12)}, + {"bar", "baz"}, + {"baz", -2.0}, + {"list", tag_list{16, 17}}}; + + // Test assignments and conversions, and exceptions on bad conversions + TS_ASSERT_EQUALS(comp["foo"].get_type(), tag_type::Short); + TS_ASSERT_EQUALS(static_cast(comp["foo"]), 12); + TS_ASSERT_EQUALS(static_cast(comp.at("foo")), int16_t(12)); + TS_ASSERT(comp["foo"] == tag_short(12)); + TS_ASSERT_THROWS(static_cast(comp["foo"]), std::bad_cast); + TS_ASSERT_THROWS(static_cast(comp["foo"]), std::bad_cast); + + TS_ASSERT_THROWS(comp["foo"] = 32, std::bad_cast); + comp["foo"] = int8_t(32); + TS_ASSERT_EQUALS(static_cast(comp["foo"]), 32); + + TS_ASSERT_EQUALS(comp["bar"].get_type(), tag_type::String); + TS_ASSERT_EQUALS(static_cast(comp["bar"]), "baz"); + TS_ASSERT_THROWS(static_cast(comp["bar"]), std::bad_cast); + + TS_ASSERT_THROWS(comp["bar"] = -128, std::bad_cast); + comp["bar"] = "barbaz"; + TS_ASSERT_EQUALS(static_cast(comp["bar"]), "barbaz"); + + TS_ASSERT_EQUALS(comp["baz"].get_type(), tag_type::Double); + TS_ASSERT_EQUALS(static_cast(comp["baz"]), -2.0); + TS_ASSERT_THROWS(static_cast(comp["baz"]), std::bad_cast); + + // Test nested access + comp["quux"] = tag_compound{{"Hello", "World"}, {"zero", 0}}; + TS_ASSERT_EQUALS(comp.at("quux").get_type(), tag_type::Compound); + TS_ASSERT_EQUALS(static_cast(comp["quux"].at("Hello")), + "World"); + TS_ASSERT_EQUALS(static_cast(comp["quux"]["Hello"]), + "World"); + TS_ASSERT(comp["list"][1] == tag_int(17)); + + TS_ASSERT_THROWS(comp.at("nothing"), std::out_of_range); + + // Test equality comparisons + tag_compound comp2{ + {"foo", int16_t(32)}, + {"bar", "barbaz"}, + {"baz", -2.0}, + {"quux", tag_compound{{"Hello", "World"}, {"zero", 0}}}, + {"list", tag_list{16, 17}}}; + TS_ASSERT(comp == comp2); + TS_ASSERT(comp != + dynamic_cast(comp2["quux"].get())); + TS_ASSERT(comp != comp2["quux"]); + TS_ASSERT(dynamic_cast(comp["quux"].get()) == + comp2["quux"]); + + // Test whether begin() through end() goes through all the keys and + // their values. The order of iteration is irrelevant there. + std::set keys{"bar", "baz", "foo", "list", "quux"}; + TS_ASSERT_EQUALS(comp2.size(), keys.size()); + unsigned int i = 0; + for (const std::pair& val : comp2) { + TS_ASSERT_LESS_THAN(i, comp2.size()); + TS_ASSERT(keys.count(val.first)); + TS_ASSERT(val.second == comp2[val.first]); + ++i; + } + TS_ASSERT_EQUALS(i, comp2.size()); + + // Test erasing and has_key + TS_ASSERT_EQUALS(comp.erase("nothing"), false); + TS_ASSERT(comp.has_key("quux")); + TS_ASSERT(comp.has_key("quux", tag_type::Compound)); + TS_ASSERT(!comp.has_key("quux", tag_type::List)); + TS_ASSERT(!comp.has_key("quux", tag_type::Null)); + + TS_ASSERT_EQUALS(comp.erase("quux"), true); + TS_ASSERT(!comp.has_key("quux")); + TS_ASSERT(!comp.has_key("quux", tag_type::Compound)); + TS_ASSERT(!comp.has_key("quux", tag_type::Null)); + + comp.clear(); + TS_ASSERT(comp == tag_compound{}); + + // Test inserting values + TS_ASSERT_EQUALS(comp.put("abc", tag_double(6.0)).second, true); + TS_ASSERT_EQUALS(comp.put("abc", tag_long(-28)).second, false); + TS_ASSERT_EQUALS(comp.insert("ghi", tag_string("world")).second, true); + TS_ASSERT_EQUALS(comp.insert("abc", tag_string("hello")).second, false); + TS_ASSERT_EQUALS(comp.emplace("def", "ghi").second, true); + TS_ASSERT_EQUALS(comp.emplace("def", 4).second, false); + TS_ASSERT((comp == tag_compound{{"abc", tag_long(-28)}, + {"def", tag_byte(4)}, + {"ghi", tag_string("world")}})); + } + + void test_value() + { + value val1; + value val2(make_unique(42)); + value val3(tag_int(42)); + + TS_ASSERT(!val1 && val2 && val3); + TS_ASSERT(val1 == val1); + TS_ASSERT(val1 != val2); + TS_ASSERT(val2 == val3); + TS_ASSERT(val3 == val3); + + value valstr(tag_string("foo")); + TS_ASSERT_EQUALS(static_cast(valstr), "foo"); + valstr = "bar"; + TS_ASSERT_THROWS(valstr = 5, std::bad_cast); + TS_ASSERT_EQUALS(static_cast(valstr), "bar"); + TS_ASSERT(valstr.as() == "bar"); + TS_ASSERT_EQUALS(&valstr.as(), &valstr.get()); + TS_ASSERT_THROWS(valstr.as(), std::bad_cast); + + val1 = int64_t(42); + TS_ASSERT(val2 != val1); + + TS_ASSERT_THROWS(val2 = int64_t(12), std::bad_cast); + TS_ASSERT_EQUALS(static_cast(val2), 42); + tag_int* ptr = dynamic_cast(val2.get_ptr().get()); + TS_ASSERT(*ptr == 42); + val2 = 52; + TS_ASSERT_EQUALS(static_cast(val2), 52); + TS_ASSERT(*ptr == 52); + + TS_ASSERT_THROWS(val1["foo"], std::bad_cast); + TS_ASSERT_THROWS(val1.at("foo"), std::bad_cast); + + val3 = 52; + TS_ASSERT(val2 == val3); + TS_ASSERT(val2.get_ptr() != val3.get_ptr()); + + val3 = std::move(val2); + TS_ASSERT(val3 == tag_int(52)); + TS_ASSERT(!val2); + + tag_int& tag = dynamic_cast(val3.get()); + TS_ASSERT(tag == tag_int(52)); + tag = 21; + TS_ASSERT_EQUALS(static_cast(val3), 21); + val1.set_ptr(std::move(val3.get_ptr())); + TS_ASSERT(val1.as() == 21); + + TS_ASSERT_EQUALS(val1.get_type(), tag_type::Int); + TS_ASSERT_EQUALS(val2.get_type(), tag_type::Null); + TS_ASSERT_EQUALS(val3.get_type(), tag_type::Null); + + val2 = val1; + val1 = val3; + TS_ASSERT(!val1 && val2 && !val3); + TS_ASSERT(val1.get_ptr() == nullptr); + TS_ASSERT(val2.get() == tag_int(21)); + TS_ASSERT(value(val1) == val1); + TS_ASSERT(value(val2) == val2); + val1 = val1; + val2 = val2; + TS_ASSERT(!val1); + TS_ASSERT(val1 == value_initializer(nullptr)); + TS_ASSERT(val2 == tag_int(21)); + + val3 = tag_short(2); + TS_ASSERT_THROWS(val3 = tag_string("foo"), std::bad_cast); + TS_ASSERT(val3.get() == tag_short(2)); + + val2.set_ptr(make_unique("foo")); + TS_ASSERT(val2 == tag_string("foo")); + } + + void test_tag_list() + { + tag_list list; + TS_ASSERT_EQUALS(list.el_type(), tag_type::Null); + TS_ASSERT_THROWS(list.push_back(value(nullptr)), std::invalid_argument); + + list.emplace_back("foo"); + TS_ASSERT_EQUALS(list.el_type(), tag_type::String); + list.push_back(tag_string("bar")); + TS_ASSERT_THROWS(list.push_back(tag_int(42)), std::invalid_argument); + TS_ASSERT_THROWS(list.emplace_back(), + std::invalid_argument); + + TS_ASSERT((list == tag_list{"foo", "bar"})); + TS_ASSERT(list[0] == tag_string("foo")); + TS_ASSERT_EQUALS(static_cast(list.at(1)), "bar"); + + TS_ASSERT_EQUALS(list.size(), 2u); + TS_ASSERT_THROWS(list.at(2), std::out_of_range); + TS_ASSERT_THROWS(list.at(-1), std::out_of_range); + + list.set(1, value(tag_string("baz"))); + TS_ASSERT_THROWS(list.set(1, value(nullptr)), std::invalid_argument); + TS_ASSERT_THROWS(list.set(1, value(tag_int(-42))), + std::invalid_argument); + TS_ASSERT_EQUALS(static_cast(list[1]), "baz"); + + TS_ASSERT_EQUALS(list.size(), 2u); + tag_string values[] = {"foo", "baz"}; + TS_ASSERT_EQUALS(list.end() - list.begin(), int(list.size())); + TS_ASSERT(std::equal(list.begin(), list.end(), values)); + + list.pop_back(); + TS_ASSERT(list == tag_list{"foo"}); + TS_ASSERT(list == tag_list::of({"foo"})); + TS_ASSERT(tag_list::of({"foo"}) == tag_list{"foo"}); + TS_ASSERT((list != tag_list{2, 3, 5, 7})); + + list.clear(); + TS_ASSERT_EQUALS(list.size(), 0u); + TS_ASSERT_EQUALS(list.el_type(), tag_type::String) + TS_ASSERT_THROWS(list.push_back(tag_short(25)), std::invalid_argument); + TS_ASSERT_THROWS(list.push_back(value(nullptr)), std::invalid_argument); + + list.reset(); + TS_ASSERT_EQUALS(list.el_type(), tag_type::Null); + list.emplace_back(17); + TS_ASSERT_EQUALS(list.el_type(), tag_type::Int); + + list.reset(tag_type::Float); + TS_ASSERT_EQUALS(list.el_type(), tag_type::Float); + list.emplace_back(17.0f); + TS_ASSERT(list == tag_list({17.0f})); + + TS_ASSERT(tag_list() != tag_list(tag_type::Int)); + TS_ASSERT(tag_list() == tag_list()); + TS_ASSERT(tag_list(tag_type::Short) != tag_list(tag_type::Int)); + TS_ASSERT(tag_list(tag_type::Short) == tag_list(tag_type::Short)); + + tag_list short_list = tag_list::of({25, 36}); + TS_ASSERT_EQUALS(short_list.el_type(), tag_type::Short); + TS_ASSERT((short_list == tag_list{int16_t(25), int16_t(36)})); + TS_ASSERT((short_list != tag_list{25, 36})); + TS_ASSERT((short_list == + tag_list{value(tag_short(25)), value(tag_short(36))})); + + TS_ASSERT_THROWS((tag_list{value(tag_byte(4)), value(tag_int(5))}), + std::invalid_argument); + TS_ASSERT_THROWS((tag_list{value(nullptr), value(tag_int(6))}), + std::invalid_argument); + TS_ASSERT_THROWS( + (tag_list{value(tag_int(7)), value(tag_int(8)), value(nullptr)}), + std::invalid_argument); + TS_ASSERT_EQUALS((tag_list(std::initializer_list{})).el_type(), + tag_type::Null); + TS_ASSERT_EQUALS((tag_list{2, 3, 5, 7}).el_type(), tag_type::Int); + } + + void test_tag_byte_array() + { + std::vector vec{1, 2, 127, -128}; + tag_byte_array arr{1, 2, 127, -128}; + TS_ASSERT_EQUALS(arr.size(), 4u); + TS_ASSERT(arr.at(0) == 1 && arr[1] == 2 && arr[2] == 127 && + arr.at(3) == -128); + TS_ASSERT_THROWS(arr.at(-1), std::out_of_range); + TS_ASSERT_THROWS(arr.at(4), std::out_of_range); + + TS_ASSERT(arr.get() == vec); + TS_ASSERT(arr == tag_byte_array(std::vector(vec))); + + arr.push_back(42); + vec.push_back(42); + + TS_ASSERT_EQUALS(arr.size(), 5u); + TS_ASSERT_EQUALS(arr.end() - arr.begin(), int(arr.size())); + TS_ASSERT(std::equal(arr.begin(), arr.end(), vec.begin())); + + arr.pop_back(); + arr.pop_back(); + TS_ASSERT_EQUALS(arr.size(), 3u); + TS_ASSERT((arr == tag_byte_array{1, 2, 127})); + TS_ASSERT((arr != tag_int_array{1, 2, 127})); + TS_ASSERT((arr != tag_long_array{1, 2, 127})); + TS_ASSERT((arr != tag_byte_array{1, 2, -1})); + + arr.clear(); + TS_ASSERT(arr == tag_byte_array()); + } + + void test_tag_int_array() + { + std::vector vec{100, 200, INT32_MAX, INT32_MIN}; + tag_int_array arr{100, 200, INT32_MAX, INT32_MIN}; + TS_ASSERT_EQUALS(arr.size(), 4u); + TS_ASSERT(arr.at(0) == 100 && arr[1] == 200 && arr[2] == INT32_MAX && + arr.at(3) == INT32_MIN); + TS_ASSERT_THROWS(arr.at(-1), std::out_of_range); + TS_ASSERT_THROWS(arr.at(4), std::out_of_range); + + TS_ASSERT(arr.get() == vec); + TS_ASSERT(arr == tag_int_array(std::vector(vec))); + + arr.push_back(42); + vec.push_back(42); + + TS_ASSERT_EQUALS(arr.size(), 5u); + TS_ASSERT_EQUALS(arr.end() - arr.begin(), int(arr.size())); + TS_ASSERT(std::equal(arr.begin(), arr.end(), vec.begin())); + + arr.pop_back(); + arr.pop_back(); + TS_ASSERT_EQUALS(arr.size(), 3u); + TS_ASSERT((arr == tag_int_array{100, 200, INT32_MAX})); + TS_ASSERT((arr != tag_int_array{100, -56, -1})); + + arr.clear(); + TS_ASSERT(arr == tag_int_array()); + } + + void test_tag_long_array() + { + std::vector vec{100, 200, INT64_MAX, INT64_MIN}; + tag_long_array arr{100, 200, INT64_MAX, INT64_MIN}; + TS_ASSERT_EQUALS(arr.size(), 4u); + TS_ASSERT(arr.at(0) == 100 && arr[1] == 200 && arr[2] == INT64_MAX && + arr.at(3) == INT64_MIN); + TS_ASSERT_THROWS(arr.at(-1), std::out_of_range); + TS_ASSERT_THROWS(arr.at(4), std::out_of_range); + + TS_ASSERT(arr.get() == vec); + TS_ASSERT(arr == tag_long_array(std::vector(vec))); + + arr.push_back(42); + vec.push_back(42); + + TS_ASSERT_EQUALS(arr.size(), 5u); + TS_ASSERT_EQUALS(arr.end() - arr.begin(), int(arr.size())); + TS_ASSERT(std::equal(arr.begin(), arr.end(), vec.begin())); + + arr.pop_back(); + arr.pop_back(); + TS_ASSERT_EQUALS(arr.size(), 3u); + TS_ASSERT((arr == tag_long_array{100, 200, INT64_MAX})); + TS_ASSERT((arr != tag_long_array{100, -56, -1})); + + arr.clear(); + TS_ASSERT(arr == tag_long_array()); + } + + void test_visitor() + { + struct : public nbt_visitor { + tag* visited = nullptr; + + void visit(tag_byte& tag) + { + visited = &tag; + } + void visit(tag_short& tag) + { + visited = &tag; + } + void visit(tag_int& tag) + { + visited = &tag; + } + void visit(tag_long& tag) + { + visited = &tag; + } + void visit(tag_float& tag) + { + visited = &tag; + } + void visit(tag_double& tag) + { + visited = &tag; + } + void visit(tag_byte_array& tag) + { + visited = &tag; + } + void visit(tag_string& tag) + { + visited = &tag; + } + void visit(tag_list& tag) + { + visited = &tag; + } + void visit(tag_compound& tag) + { + visited = &tag; + } + void visit(tag_int_array& tag) + { + visited = &tag; + } + void visit(tag_long_array& tag) + { + visited = &tag; + } + } v; + + tag_byte b; + b.accept(v); + TS_ASSERT_EQUALS(v.visited, &b); + tag_short s; + s.accept(v); + TS_ASSERT_EQUALS(v.visited, &s); + tag_int i; + i.accept(v); + TS_ASSERT_EQUALS(v.visited, &i); + tag_long l; + l.accept(v); + TS_ASSERT_EQUALS(v.visited, &l); + tag_float f; + f.accept(v); + TS_ASSERT_EQUALS(v.visited, &f); + tag_double d; + d.accept(v); + TS_ASSERT_EQUALS(v.visited, &d); + tag_byte_array ba; + ba.accept(v); + TS_ASSERT_EQUALS(v.visited, &ba); + tag_string st; + st.accept(v); + TS_ASSERT_EQUALS(v.visited, &st); + tag_list ls; + ls.accept(v); + TS_ASSERT_EQUALS(v.visited, &ls); + tag_compound c; + c.accept(v); + TS_ASSERT_EQUALS(v.visited, &c); + tag_int_array ia; + ia.accept(v); + TS_ASSERT_EQUALS(v.visited, &ia); + tag_long_array la; + la.accept(v); + TS_ASSERT_EQUALS(v.visited, &la); + } }; diff --git a/test/read_test.h b/test/read_test.h index d8750f5cb6..a4e74ac248 100644 --- a/test/read_test.h +++ b/test/read_test.h @@ -39,218 +39,242 @@ using namespace nbt; class read_test : public CxxTest::TestSuite { -public: - void test_stream_reader_big() - { - std::string input{ - 1, //tag_type::Byte - 0, //tag_type::End - 11, //tag_type::Int_Array - - 0x0a, 0x0b, 0x0c, 0x0d, //0x0a0b0c0d in Big Endian - - 0x00, 0x06, //String length in Big Endian - 'f', 'o', 'o', 'b', 'a', 'r', - - 0 //tag_type::End (invalid with allow_end = false) - }; - std::istringstream is(input); - nbt::io::stream_reader reader(is); - - TS_ASSERT_EQUALS(&reader.get_istr(), &is); - TS_ASSERT_EQUALS(reader.get_endian(), endian::big); - - TS_ASSERT_EQUALS(reader.read_type(), tag_type::Byte); - TS_ASSERT_EQUALS(reader.read_type(true), tag_type::End); - TS_ASSERT_EQUALS(reader.read_type(false), tag_type::Int_Array); - - int32_t i; - reader.read_num(i); - TS_ASSERT_EQUALS(i, 0x0a0b0c0d); - - TS_ASSERT_EQUALS(reader.read_string(), "foobar"); - - TS_ASSERT_THROWS(reader.read_type(false), io::input_error); - TS_ASSERT(!is); - is.clear(); - - //Test for invalid tag type 13 - is.str("\x0d"); - TS_ASSERT_THROWS(reader.read_type(), io::input_error); - TS_ASSERT(!is); - is.clear(); - - //Test for unexpcted EOF on numbers (input too short for int32_t) - is.str("\x03\x04"); - reader.read_num(i); - TS_ASSERT(!is); - } - - void test_stream_reader_little() - { - std::string input{ - 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, //0x0d0c0b0a09080706 in Little Endian - - 0x06, 0x00, //String length in Little Endian - 'f', 'o', 'o', 'b', 'a', 'r', - - 0x10, 0x00, //String length (intentionally too large) - 'a', 'b', 'c', 'd' //unexpected EOF - }; - std::istringstream is(input); - nbt::io::stream_reader reader(is, endian::little); - - TS_ASSERT_EQUALS(reader.get_endian(), endian::little); - - int64_t i; - reader.read_num(i); - TS_ASSERT_EQUALS(i, 0x0d0c0b0a09080706); - - TS_ASSERT_EQUALS(reader.read_string(), "foobar"); - - TS_ASSERT_THROWS(reader.read_string(), io::input_error); - TS_ASSERT(!is); - } - - //Tests if comp equals an extended variant of Notch's bigtest NBT - void verify_bigtest_structure(const tag_compound& comp) - { - TS_ASSERT_EQUALS(comp.size(), 13u); - - TS_ASSERT(comp.at("byteTest") == tag_byte(127)); - TS_ASSERT(comp.at("shortTest") == tag_short(32767)); - TS_ASSERT(comp.at("intTest") == tag_int(2147483647)); - TS_ASSERT(comp.at("longTest") == tag_long(9223372036854775807)); - TS_ASSERT(comp.at("floatTest") == tag_float(std::stof("0xff1832p-25"))); //0.4982315 - TS_ASSERT(comp.at("doubleTest") == tag_double(std::stod("0x1f8f6bbbff6a5ep-54"))); //0.493128713218231 - - //From bigtest.nbt: "the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...)" - tag_byte_array byteArrayTest; - for(int n = 0; n < 1000; ++n) - byteArrayTest.push_back((n*n*255 + n*7) % 100); - TS_ASSERT(comp.at("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))") == byteArrayTest); - - TS_ASSERT(comp.at("stringTest") == tag_string("HELLO WORLD THIS IS A TEST STRING \u00C5\u00C4\u00D6!")); - - TS_ASSERT(comp.at("listTest (compound)") == tag_list::of({ - {{"created-on", tag_long(1264099775885)}, {"name", "Compound tag #0"}}, - {{"created-on", tag_long(1264099775885)}, {"name", "Compound tag #1"}} - })); - TS_ASSERT(comp.at("listTest (long)") == tag_list::of({11, 12, 13, 14, 15})); - TS_ASSERT(comp.at("listTest (end)") == tag_list()); - - TS_ASSERT((comp.at("nested compound test") == tag_compound{ - {"egg", tag_compound{{"value", 0.5f}, {"name", "Eggbert"}}}, - {"ham", tag_compound{{"value", 0.75f}, {"name", "Hampus"}}} - })); - - TS_ASSERT(comp.at("intArrayTest") == tag_int_array( - {0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f})); - } - - void test_read_bigtest() - { - //Uses an extended variant of Notch's original bigtest file - std::string input(__binary_bigtest_uncompr_start, __binary_bigtest_uncompr_end); - std::istringstream file(input, std::ios::binary); - - auto pair = nbt::io::read_compound(file); - TS_ASSERT_EQUALS(pair.first, "Level"); - verify_bigtest_structure(*pair.second); - } - - void test_read_littletest() - { - //Same as bigtest, but little endian - std::string input(__binary_littletest_uncompr_start, __binary_littletest_uncompr_end); - std::istringstream file(input, std::ios::binary); - - auto pair = nbt::io::read_compound(file, endian::little); - TS_ASSERT_EQUALS(pair.first, "Level"); - TS_ASSERT_EQUALS(pair.second->get_type(), tag_type::Compound); - verify_bigtest_structure(*pair.second); - } - - void test_read_eof1() - { - std::string input(__binary_errortest_eof1_start, __binary_errortest_eof1_end); - std::istringstream file(input, std::ios::binary); - nbt::io::stream_reader reader(file); - - //EOF within a tag_double payload - TS_ASSERT(file); - TS_ASSERT_THROWS(reader.read_tag(), io::input_error); - TS_ASSERT(!file); - } - - void test_read_eof2() - { - std::string input(__binary_errortest_eof2_start, __binary_errortest_eof2_end); - std::istringstream file(input, std::ios::binary); - nbt::io::stream_reader reader(file); - - //EOF within a key in a compound - TS_ASSERT(file); - TS_ASSERT_THROWS(reader.read_tag(), io::input_error); - TS_ASSERT(!file); - } - - void test_read_errortest_noend() - { - std::string input(__binary_errortest_noend_start, __binary_errortest_noend_end); - std::istringstream file(input, std::ios::binary); - nbt::io::stream_reader reader(file); - - //Missing tag_end - TS_ASSERT(file); - TS_ASSERT_THROWS(reader.read_tag(), io::input_error); - TS_ASSERT(!file); - } - - void test_read_errortest_neg_length() - { - std::string input(__binary_errortest_neg_length_start, __binary_errortest_neg_length_end); - std::istringstream file(input, std::ios::binary); - nbt::io::stream_reader reader(file); - - //Negative list length - TS_ASSERT(file); - TS_ASSERT_THROWS(reader.read_tag(), io::input_error); - TS_ASSERT(!file); - } - - void test_read_misc() - { - std::string input(__binary_toplevel_string_start, __binary_toplevel_string_end); - std::istringstream file(input, std::ios::binary); - nbt::io::stream_reader reader(file); - - //Toplevel tag other than compound - TS_ASSERT(file); - TS_ASSERT_THROWS(reader.read_compound(), io::input_error); - TS_ASSERT(!file); - - //Rewind and try again with read_tag - file.clear(); - TS_ASSERT(file.seekg(0)); - auto pair = reader.read_tag(); - TS_ASSERT_EQUALS(pair.first, "Test (toplevel tag_string)"); - TS_ASSERT(*pair.second == tag_string( - "Even though unprovided for by NBT, the library should also handle " - "the case where the file consists of something else than tag_compound")); - } - void test_read_gzip() - { + public: + void test_stream_reader_big() + { + std::string input{ + 1, // tag_type::Byte + 0, // tag_type::End + 11, // tag_type::Int_Array + + 0x0a, 0x0b, 0x0c, 0x0d, // 0x0a0b0c0d in Big Endian + + 0x00, 0x06, // String length in Big Endian + 'f', 'o', 'o', 'b', 'a', 'r', + + 0 // tag_type::End (invalid with allow_end = false) + }; + std::istringstream is(input); + nbt::io::stream_reader reader(is); + + TS_ASSERT_EQUALS(&reader.get_istr(), &is); + TS_ASSERT_EQUALS(reader.get_endian(), endian::big); + + TS_ASSERT_EQUALS(reader.read_type(), tag_type::Byte); + TS_ASSERT_EQUALS(reader.read_type(true), tag_type::End); + TS_ASSERT_EQUALS(reader.read_type(false), tag_type::Int_Array); + + int32_t i; + reader.read_num(i); + TS_ASSERT_EQUALS(i, 0x0a0b0c0d); + + TS_ASSERT_EQUALS(reader.read_string(), "foobar"); + + TS_ASSERT_THROWS(reader.read_type(false), io::input_error); + TS_ASSERT(!is); + is.clear(); + + // Test for invalid tag type 13 + is.str("\x0d"); + TS_ASSERT_THROWS(reader.read_type(), io::input_error); + TS_ASSERT(!is); + is.clear(); + + // Test for unexpcted EOF on numbers (input too short for int32_t) + is.str("\x03\x04"); + reader.read_num(i); + TS_ASSERT(!is); + } + + void test_stream_reader_little() + { + std::string input{ + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, // 0x0d0c0b0a09080706 in Little Endian + + 0x06, 0x00, // String length in Little Endian + 'f', 'o', 'o', 'b', 'a', 'r', + + 0x10, 0x00, // String length (intentionally too large) + 'a', 'b', 'c', 'd' // unexpected EOF + }; + std::istringstream is(input); + nbt::io::stream_reader reader(is, endian::little); + + TS_ASSERT_EQUALS(reader.get_endian(), endian::little); + + int64_t i; + reader.read_num(i); + TS_ASSERT_EQUALS(i, 0x0d0c0b0a09080706); + + TS_ASSERT_EQUALS(reader.read_string(), "foobar"); + + TS_ASSERT_THROWS(reader.read_string(), io::input_error); + TS_ASSERT(!is); + } + + // Tests if comp equals an extended variant of Notch's bigtest NBT + void verify_bigtest_structure(const tag_compound& comp) + { + TS_ASSERT_EQUALS(comp.size(), 13u); + + TS_ASSERT(comp.at("byteTest") == tag_byte(127)); + TS_ASSERT(comp.at("shortTest") == tag_short(32767)); + TS_ASSERT(comp.at("intTest") == tag_int(2147483647)); + TS_ASSERT(comp.at("longTest") == tag_long(9223372036854775807)); + TS_ASSERT(comp.at("floatTest") == + tag_float(std::stof("0xff1832p-25"))); // 0.4982315 + TS_ASSERT( + comp.at("doubleTest") == + tag_double(std::stod("0x1f8f6bbbff6a5ep-54"))); // 0.493128713218231 + + // From bigtest.nbt: "the first 1000 values of (n*n*255+n*7)%100, + // starting with n=0 (0, 62, 34, 16, 8, ...)" + tag_byte_array byteArrayTest; + for (int n = 0; n < 1000; ++n) + byteArrayTest.push_back((n * n * 255 + n * 7) % 100); + TS_ASSERT( + comp.at( + "byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, " + "starting with n=0 (0, 62, 34, 16, 8, ...))") == byteArrayTest); + + TS_ASSERT(comp.at("stringTest") == + tag_string( + "HELLO WORLD THIS IS A TEST STRING \u00C5\u00C4\u00D6!")); + + TS_ASSERT(comp.at("listTest (compound)") == + tag_list::of( + {{{"created-on", tag_long(1264099775885)}, + {"name", "Compound tag #0"}}, + {{"created-on", tag_long(1264099775885)}, + {"name", "Compound tag #1"}}})); + TS_ASSERT(comp.at("listTest (long)") == + tag_list::of({11, 12, 13, 14, 15})); + TS_ASSERT(comp.at("listTest (end)") == tag_list()); + + TS_ASSERT( + (comp.at("nested compound test") == + tag_compound{ + {"egg", tag_compound{{"value", 0.5f}, {"name", "Eggbert"}}}, + {"ham", tag_compound{{"value", 0.75f}, {"name", "Hampus"}}}})); + + TS_ASSERT( + comp.at("intArrayTest") == + tag_int_array({0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f})); + } + + void test_read_bigtest() + { + // Uses an extended variant of Notch's original bigtest file + std::string input(__binary_bigtest_uncompr_start, + __binary_bigtest_uncompr_end); + std::istringstream file(input, std::ios::binary); + + auto pair = nbt::io::read_compound(file); + TS_ASSERT_EQUALS(pair.first, "Level"); + verify_bigtest_structure(*pair.second); + } + + void test_read_littletest() + { + // Same as bigtest, but little endian + std::string input(__binary_littletest_uncompr_start, + __binary_littletest_uncompr_end); + std::istringstream file(input, std::ios::binary); + + auto pair = nbt::io::read_compound(file, endian::little); + TS_ASSERT_EQUALS(pair.first, "Level"); + TS_ASSERT_EQUALS(pair.second->get_type(), tag_type::Compound); + verify_bigtest_structure(*pair.second); + } + + void test_read_eof1() + { + std::string input(__binary_errortest_eof1_start, + __binary_errortest_eof1_end); + std::istringstream file(input, std::ios::binary); + nbt::io::stream_reader reader(file); + + // EOF within a tag_double payload + TS_ASSERT(file); + TS_ASSERT_THROWS(reader.read_tag(), io::input_error); + TS_ASSERT(!file); + } + + void test_read_eof2() + { + std::string input(__binary_errortest_eof2_start, + __binary_errortest_eof2_end); + std::istringstream file(input, std::ios::binary); + nbt::io::stream_reader reader(file); + + // EOF within a key in a compound + TS_ASSERT(file); + TS_ASSERT_THROWS(reader.read_tag(), io::input_error); + TS_ASSERT(!file); + } + + void test_read_errortest_noend() + { + std::string input(__binary_errortest_noend_start, + __binary_errortest_noend_end); + std::istringstream file(input, std::ios::binary); + nbt::io::stream_reader reader(file); + + // Missing tag_end + TS_ASSERT(file); + TS_ASSERT_THROWS(reader.read_tag(), io::input_error); + TS_ASSERT(!file); + } + + void test_read_errortest_neg_length() + { + std::string input(__binary_errortest_neg_length_start, + __binary_errortest_neg_length_end); + std::istringstream file(input, std::ios::binary); + nbt::io::stream_reader reader(file); + + // Negative list length + TS_ASSERT(file); + TS_ASSERT_THROWS(reader.read_tag(), io::input_error); + TS_ASSERT(!file); + } + + void test_read_misc() + { + std::string input(__binary_toplevel_string_start, + __binary_toplevel_string_end); + std::istringstream file(input, std::ios::binary); + nbt::io::stream_reader reader(file); + + // Toplevel tag other than compound + TS_ASSERT(file); + TS_ASSERT_THROWS(reader.read_compound(), io::input_error); + TS_ASSERT(!file); + + // Rewind and try again with read_tag + file.clear(); + TS_ASSERT(file.seekg(0)); + auto pair = reader.read_tag(); + TS_ASSERT_EQUALS(pair.first, "Test (toplevel tag_string)"); + TS_ASSERT(*pair.second == + tag_string("Even though unprovided for by NBT, the library " + "should also handle " + "the case where the file consists of something " + "else than tag_compound")); + } + void test_read_gzip() + { #ifdef NBT_HAVE_ZLIB - std::string input(__binary_bigtest_nbt_start, __binary_bigtest_nbt_end); - std::istringstream file(input, std::ios::binary); - zlib::izlibstream igzs(file); - TS_ASSERT(file && igzs); - - auto pair = nbt::io::read_compound(igzs); - TS_ASSERT(igzs); - TS_ASSERT_EQUALS(pair.first, "Level"); - verify_bigtest_structure(*pair.second); + std::string input(__binary_bigtest_nbt_start, __binary_bigtest_nbt_end); + std::istringstream file(input, std::ios::binary); + zlib::izlibstream igzs(file); + TS_ASSERT(file && igzs); + + auto pair = nbt::io::read_compound(igzs); + TS_ASSERT(igzs); + TS_ASSERT_EQUALS(pair.first, "Level"); + verify_bigtest_structure(*pair.second); #endif - } + } }; diff --git a/test/test_value.h b/test/test_value.h index f63d051d28..590c5bf2cc 100644 --- a/test/test_value.h +++ b/test/test_value.h @@ -6,31 +6,31 @@ using namespace nbt; class value_assignment_test : public CxxTest::TestSuite { -public: - void test_numeric_assignments() - { - value v; + public: + void test_numeric_assignments() + { + value v; - v = int8_t(-5); - TS_ASSERT_EQUALS(int32_t(v), int32_t(-5)); - TS_ASSERT_EQUALS(double(v), 5.); + v = int8_t(-5); + TS_ASSERT_EQUALS(int32_t(v), int32_t(-5)); + TS_ASSERT_EQUALS(double(v), 5.); - v = value(); - v = int16_t(12345); - TS_ASSERT_EQUALS(int32_t(v), int32_t(12345)); - TS_ASSERT_EQUALS(double(v), 12345.); + v = value(); + v = int16_t(12345); + TS_ASSERT_EQUALS(int32_t(v), int32_t(12345)); + TS_ASSERT_EQUALS(double(v), 12345.); - v = value(); - v = int32_t(100000); - TS_ASSERT_EQUALS(int64_t(v), int64_t(100000)); - TS_ASSERT_EQUALS(double(v), 100000.); + v = value(); + v = int32_t(100000); + TS_ASSERT_EQUALS(int64_t(v), int64_t(100000)); + TS_ASSERT_EQUALS(double(v), 100000.); - v = value(); - v = float(3.14f); - TS_ASSERT_EQUALS(double(v), 3.14); + v = value(); + v = float(3.14f); + TS_ASSERT_EQUALS(double(v), 3.14); - v = value(); - v = double(2.718281828); - TS_ASSERT_EQUALS(double(v), 2.718281828); - } + v = value(); + v = double(2.718281828); + TS_ASSERT_EQUALS(double(v), 2.718281828); + } }; diff --git a/test/write_test.h b/test/write_test.h index 376eafa59d..fa41a6092d 100644 --- a/test/write_test.h +++ b/test/write_test.h @@ -41,238 +41,227 @@ using namespace nbt; class read_test : public CxxTest::TestSuite { -public: - void test_stream_writer_big() - { - std::ostringstream os; - nbt::io::stream_writer writer(os); - - TS_ASSERT_EQUALS(&writer.get_ostr(), &os); - TS_ASSERT_EQUALS(writer.get_endian(), endian::big); - - writer.write_type(tag_type::End); - writer.write_type(tag_type::Long); - writer.write_type(tag_type::Int_Array); - - writer.write_num(int64_t(0x0102030405060708)); - - writer.write_string("foobar"); - - TS_ASSERT(os); - std::string expected{ - 0, //tag_type::End - 4, //tag_type::Long - 11, //tag_type::Int_Array - - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, //0x0102030405060708 in Big Endian - - 0x00, 0x06, //string length in Big Endian - 'f', 'o', 'o', 'b', 'a', 'r' - }; - TS_ASSERT_EQUALS(os.str(), expected); - - //too long for NBT - TS_ASSERT_THROWS(writer.write_string(std::string(65536, '.')), std::length_error); - TS_ASSERT(!os); - } - - void test_stream_writer_little() - { - std::ostringstream os; - nbt::io::stream_writer writer(os, endian::little); - - TS_ASSERT_EQUALS(writer.get_endian(), endian::little); - - writer.write_num(int32_t(0x0a0b0c0d)); - - writer.write_string("foobar"); - - TS_ASSERT(os); - std::string expected{ - 0x0d, 0x0c, 0x0b, 0x0a, //0x0a0b0c0d in Little Endian - - 0x06, 0x00, //string length in Little Endian - 'f', 'o', 'o', 'b', 'a', 'r' - }; - TS_ASSERT_EQUALS(os.str(), expected); - - TS_ASSERT_THROWS(writer.write_string(std::string(65536, '.')), std::length_error); - TS_ASSERT(!os); - } - - void test_write_payload_big() - { - std::ostringstream os; - nbt::io::stream_writer writer(os); - - //tag_primitive - writer.write_payload(tag_byte(127)); - writer.write_payload(tag_short(32767)); - writer.write_payload(tag_int(2147483647)); - writer.write_payload(tag_long(9223372036854775807)); - - //Same values as in endian_str_test - writer.write_payload(tag_float(std::stof("-0xCDEF01p-63"))); - writer.write_payload(tag_double(std::stod("-0x1DEF0102030405p-375"))); - - TS_ASSERT_EQUALS(os.str(), (std::string{ - '\x7F', - '\x7F', '\xFF', - '\x7F', '\xFF', '\xFF', '\xFF', - '\x7F', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', - - '\xAB', '\xCD', '\xEF', '\x01', - '\xAB', '\xCD', '\xEF', '\x01', '\x02', '\x03', '\x04', '\x05' - })); - os.str(""); //clear and reuse the stream - - //tag_string - writer.write_payload(tag_string("barbaz")); - TS_ASSERT_EQUALS(os.str(), (std::string{ - 0x00, 0x06, //string length in Big Endian - 'b', 'a', 'r', 'b', 'a', 'z' - })); - TS_ASSERT_THROWS(writer.write_payload(tag_string(std::string(65536, '.'))), std::length_error); - TS_ASSERT(!os); - os.clear(); - - //tag_byte_array - os.str(""); - writer.write_payload(tag_byte_array{0, 1, 127, -128, -127}); - TS_ASSERT_EQUALS(os.str(), (std::string{ - 0x00, 0x00, 0x00, 0x05, //length in Big Endian - 0, 1, 127, -128, -127 - })); - os.str(""); - - //tag_int_array - writer.write_payload(tag_int_array{0x01020304, 0x05060708, 0x090a0b0c}); - TS_ASSERT_EQUALS(os.str(), (std::string{ - 0x00, 0x00, 0x00, 0x03, //length in Big Endian - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c - })); - os.str(""); - - //tag_list - writer.write_payload(tag_list()); //empty list with undetermined type, should be written as list of tag_end - writer.write_payload(tag_list(tag_type::Int)); //empty list of tag_int - writer.write_payload(tag_list{ //nested list - tag_list::of({0x3456, 0x789a}), - tag_list::of({0x0a, 0x0b, 0x0c, 0x0d}) - }); - TS_ASSERT_EQUALS(os.str(), (std::string{ - 0, //tag_type::End - 0x00, 0x00, 0x00, 0x00, //length - - 3, //tag_type::Int - 0x00, 0x00, 0x00, 0x00, //length - - 9, //tag_type::List - 0x00, 0x00, 0x00, 0x02, //length - //list 0 - 2, //tag_type::Short - 0x00, 0x00, 0x00, 0x02, //length - '\x34', '\x56', - '\x78', '\x9a', - //list 1 - 1, //tag_type::Byte - 0x00, 0x00, 0x00, 0x04, //length - 0x0a, - 0x0b, - 0x0c, - 0x0d - })); - os.str(""); - - //tag_compound - /* Testing if writing compounds works properly is problematic because the - order of the tags is not guaranteed. However with only two tags in a - compound we only have two possible orderings. - See below for a more thorough test that uses writing and re-reading. */ - writer.write_payload(tag_compound{}); - writer.write_payload(tag_compound{ - {"foo", "quux"}, - {"bar", tag_int(0x789abcde)} - }); - - std::string endtag{0x00}; - std::string subtag1{ - 8, //tag_type::String - 0x00, 0x03, //key length - 'f', 'o', 'o', - 0x00, 0x04, //string length - 'q', 'u', 'u', 'x' - }; - std::string subtag2{ - 3, //tag_type::Int - 0x00, 0x03, //key length - 'b', 'a', 'r', - '\x78', '\x9A', '\xBC', '\xDE' - }; - - TS_ASSERT(os.str() == endtag + subtag1 + subtag2 + endtag - || os.str() == endtag + subtag2 + subtag1 + endtag); - - //Now for write_tag: - os.str(""); - writer.write_tag("foo", tag_string("quux")); - TS_ASSERT_EQUALS(os.str(), subtag1); - TS_ASSERT(os); - - //too long key for NBT - TS_ASSERT_THROWS(writer.write_tag(std::string(65536, '.'), tag_long(-1)), std::length_error); - TS_ASSERT(!os); - } - - void test_write_bigtest() - { - /* Like already stated above, because no order is guaranteed for - tag_compound, we cannot simply test it by writing into a stream and directly - comparing the output to a reference value. - Instead, we assume that reading already works correctly and re-read the - written tag. - Smaller-grained tests are already done above. */ - std::string input(__binary_bigtest_uncompr_start, __binary_bigtest_uncompr_end); - std::istringstream file(input, std::ios::binary); - - const auto orig_pair = io::read_compound(file); - std::stringstream sstr; - - //Write into stream in Big Endian - io::write_tag(orig_pair.first, *orig_pair.second, sstr); - TS_ASSERT(sstr); - - //Read from stream in Big Endian and compare - auto written_pair = io::read_compound(sstr); - TS_ASSERT_EQUALS(orig_pair.first, written_pair.first); - TS_ASSERT(*orig_pair.second == *written_pair.second); - - sstr.str(""); //Reset and reuse stream - //Write into stream in Little Endian - io::write_tag(orig_pair.first, *orig_pair.second, sstr, endian::little); - TS_ASSERT(sstr); - - //Read from stream in Little Endian and compare - written_pair = io::read_compound(sstr, endian::little); - TS_ASSERT_EQUALS(orig_pair.first, written_pair.first); - TS_ASSERT(*orig_pair.second == *written_pair.second); + public: + void test_stream_writer_big() + { + std::ostringstream os; + nbt::io::stream_writer writer(os); + + TS_ASSERT_EQUALS(&writer.get_ostr(), &os); + TS_ASSERT_EQUALS(writer.get_endian(), endian::big); + + writer.write_type(tag_type::End); + writer.write_type(tag_type::Long); + writer.write_type(tag_type::Int_Array); + + writer.write_num(int64_t(0x0102030405060708)); + + writer.write_string("foobar"); + + TS_ASSERT(os); + std::string expected{0, // tag_type::End + 4, // tag_type::Long + 11, // tag_type::Int_Array + + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, // 0x0102030405060708 in Big Endian + + 0x00, 0x06, // string length in Big Endian + 'f', 'o', 'o', 'b', 'a', 'r'}; + TS_ASSERT_EQUALS(os.str(), expected); + + // too long for NBT + TS_ASSERT_THROWS(writer.write_string(std::string(65536, '.')), + std::length_error); + TS_ASSERT(!os); + } + + void test_stream_writer_little() + { + std::ostringstream os; + nbt::io::stream_writer writer(os, endian::little); + + TS_ASSERT_EQUALS(writer.get_endian(), endian::little); + + writer.write_num(int32_t(0x0a0b0c0d)); + + writer.write_string("foobar"); + + TS_ASSERT(os); + std::string expected{ + 0x0d, 0x0c, 0x0b, 0x0a, // 0x0a0b0c0d in Little Endian + + 0x06, 0x00, // string length in Little Endian + 'f', 'o', 'o', 'b', 'a', 'r'}; + TS_ASSERT_EQUALS(os.str(), expected); + + TS_ASSERT_THROWS(writer.write_string(std::string(65536, '.')), + std::length_error); + TS_ASSERT(!os); + } + + void test_write_payload_big() + { + std::ostringstream os; + nbt::io::stream_writer writer(os); + + // tag_primitive + writer.write_payload(tag_byte(127)); + writer.write_payload(tag_short(32767)); + writer.write_payload(tag_int(2147483647)); + writer.write_payload(tag_long(9223372036854775807)); + + // Same values as in endian_str_test + writer.write_payload(tag_float(std::stof("-0xCDEF01p-63"))); + writer.write_payload(tag_double(std::stod("-0x1DEF0102030405p-375"))); + + TS_ASSERT_EQUALS( + os.str(), + (std::string{'\x7F', '\x7F', '\xFF', '\x7F', '\xFF', '\xFF', + '\xFF', '\x7F', '\xFF', '\xFF', '\xFF', '\xFF', + '\xFF', '\xFF', '\xFF', + + '\xAB', '\xCD', '\xEF', '\x01', '\xAB', '\xCD', + '\xEF', '\x01', '\x02', '\x03', '\x04', '\x05'})); + os.str(""); // clear and reuse the stream + + // tag_string + writer.write_payload(tag_string("barbaz")); + TS_ASSERT_EQUALS(os.str(), + (std::string{0x00, 0x06, // string length in Big Endian + 'b', 'a', 'r', 'b', 'a', 'z'})); + TS_ASSERT_THROWS( + writer.write_payload(tag_string(std::string(65536, '.'))), + std::length_error); + TS_ASSERT(!os); + os.clear(); + + // tag_byte_array + os.str(""); + writer.write_payload(tag_byte_array{0, 1, 127, -128, -127}); + TS_ASSERT_EQUALS(os.str(), (std::string{0x00, 0x00, 0x00, + 0x05, // length in Big Endian + 0, 1, 127, -128, -127})); + os.str(""); + + // tag_int_array + writer.write_payload(tag_int_array{0x01020304, 0x05060708, 0x090a0b0c}); + TS_ASSERT_EQUALS( + os.str(), + (std::string{0x00, 0x00, 0x00, 0x03, // length in Big Endian + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c})); + os.str(""); + + // tag_list + writer.write_payload( + tag_list()); // empty list with undetermined type, should be written + // as list of tag_end + writer.write_payload(tag_list(tag_type::Int)); // empty list of tag_int + writer.write_payload( + tag_list{// nested list + tag_list::of({0x3456, 0x789a}), + tag_list::of({0x0a, 0x0b, 0x0c, 0x0d})}); + TS_ASSERT_EQUALS(os.str(), + (std::string{0, // tag_type::End + 0x00, 0x00, 0x00, 0x00, // length + + 3, // tag_type::Int + 0x00, 0x00, 0x00, 0x00, // length + + 9, // tag_type::List + 0x00, 0x00, 0x00, 0x02, // length + // list 0 + 2, // tag_type::Short + 0x00, 0x00, 0x00, 0x02, // length + '\x34', '\x56', '\x78', '\x9a', + // list 1 + 1, // tag_type::Byte + 0x00, 0x00, 0x00, 0x04, // length + 0x0a, 0x0b, 0x0c, 0x0d})); + os.str(""); + + // tag_compound + /* Testing if writing compounds works properly is problematic because + the order of the tags is not guaranteed. However with only two tags in a + compound we only have two possible orderings. + See below for a more thorough test that uses writing and re-reading. */ + writer.write_payload(tag_compound{}); + writer.write_payload( + tag_compound{{"foo", "quux"}, {"bar", tag_int(0x789abcde)}}); + + std::string endtag{0x00}; + std::string subtag1{8, // tag_type::String + 0x00, 0x03, // key length + 'f', 'o', 'o', 0x00, 0x04, // string length + 'q', 'u', 'u', 'x'}; + std::string subtag2{3, // tag_type::Int + 0x00, 0x03, // key length + 'b', 'a', 'r', '\x78', '\x9A', '\xBC', '\xDE'}; + + TS_ASSERT(os.str() == endtag + subtag1 + subtag2 + endtag || + os.str() == endtag + subtag2 + subtag1 + endtag); + + // Now for write_tag: + os.str(""); + writer.write_tag("foo", tag_string("quux")); + TS_ASSERT_EQUALS(os.str(), subtag1); + TS_ASSERT(os); + + // too long key for NBT + TS_ASSERT_THROWS( + writer.write_tag(std::string(65536, '.'), tag_long(-1)), + std::length_error); + TS_ASSERT(!os); + } + + void test_write_bigtest() + { + /* Like already stated above, because no order is guaranteed for + tag_compound, we cannot simply test it by writing into a stream and + directly comparing the output to a reference value. Instead, we assume + that reading already works correctly and re-read the written tag. + Smaller-grained tests are already done above. */ + std::string input(__binary_bigtest_uncompr_start, + __binary_bigtest_uncompr_end); + std::istringstream file(input, std::ios::binary); + + const auto orig_pair = io::read_compound(file); + std::stringstream sstr; + + // Write into stream in Big Endian + io::write_tag(orig_pair.first, *orig_pair.second, sstr); + TS_ASSERT(sstr); + + // Read from stream in Big Endian and compare + auto written_pair = io::read_compound(sstr); + TS_ASSERT_EQUALS(orig_pair.first, written_pair.first); + TS_ASSERT(*orig_pair.second == *written_pair.second); + + sstr.str(""); // Reset and reuse stream + // Write into stream in Little Endian + io::write_tag(orig_pair.first, *orig_pair.second, sstr, endian::little); + TS_ASSERT(sstr); + + // Read from stream in Little Endian and compare + written_pair = io::read_compound(sstr, endian::little); + TS_ASSERT_EQUALS(orig_pair.first, written_pair.first); + TS_ASSERT(*orig_pair.second == *written_pair.second); #ifdef NBT_HAVE_ZLIB - //Now with gzip compression - sstr.str(""); - zlib::ozlibstream ogzs(sstr, -1, true); - io::write_tag(orig_pair.first, *orig_pair.second, ogzs); - ogzs.close(); - TS_ASSERT(ogzs); - TS_ASSERT(sstr); - //Read and compare - zlib::izlibstream igzs(sstr); - written_pair = io::read_compound(igzs); - TS_ASSERT(igzs); - TS_ASSERT_EQUALS(orig_pair.first, written_pair.first); - TS_ASSERT(*orig_pair.second == *written_pair.second); + // Now with gzip compression + sstr.str(""); + zlib::ozlibstream ogzs(sstr, -1, true); + io::write_tag(orig_pair.first, *orig_pair.second, ogzs); + ogzs.close(); + TS_ASSERT(ogzs); + TS_ASSERT(sstr); + // Read and compare + zlib::izlibstream igzs(sstr); + written_pair = io::read_compound(igzs); + TS_ASSERT(igzs); + TS_ASSERT_EQUALS(orig_pair.first, written_pair.first); + TS_ASSERT(*orig_pair.second == *written_pair.second); #endif - } + } }; diff --git a/test/zlibstream_test.h b/test/zlibstream_test.h index 67a608e2ad..c96797ce90 100644 --- a/test/zlibstream_test.h +++ b/test/zlibstream_test.h @@ -35,249 +35,260 @@ using namespace zlib; class zlibstream_test : public CxxTest::TestSuite { -private: - std::string bigtest; + private: + std::string bigtest; -public: - zlibstream_test() - { - std::string input(__binary_bigtest_uncompr_start, __binary_bigtest_uncompr_end); - std::istringstream bigtest_f(input, std::ios::binary); - std::stringbuf bigtest_b; - bigtest_f >> &bigtest_b; - bigtest = bigtest_b.str(); - if(!bigtest_f || bigtest.size() == 0) - throw std::runtime_error("Could not read bigtest_uncompr file"); - } + public: + zlibstream_test() + { + std::string input(__binary_bigtest_uncompr_start, + __binary_bigtest_uncompr_end); + std::istringstream bigtest_f(input, std::ios::binary); + std::stringbuf bigtest_b; + bigtest_f >> &bigtest_b; + bigtest = bigtest_b.str(); + if (!bigtest_f || bigtest.size() == 0) + throw std::runtime_error("Could not read bigtest_uncompr file"); + } - void test_inflate_gzip() - { - std::string input(__binary_bigtest_nbt_start, __binary_bigtest_nbt_end); - std::istringstream gzip_in(input, std::ios::binary); - TS_ASSERT(gzip_in); + void test_inflate_gzip() + { + std::string input(__binary_bigtest_nbt_start, __binary_bigtest_nbt_end); + std::istringstream gzip_in(input, std::ios::binary); + TS_ASSERT(gzip_in); - std::stringbuf data; - //Small buffer so not all fits at once (the compressed file is 561 bytes) - { - izlibstream igzs(gzip_in, 256); - igzs.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT(igzs.good()); + std::stringbuf data; + // Small buffer so not all fits at once (the compressed file is 561 + // bytes) + { + izlibstream igzs(gzip_in, 256); + igzs.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT(igzs.good()); - TS_ASSERT_THROWS_NOTHING(igzs >> &data); - TS_ASSERT(igzs); - TS_ASSERT(igzs.eof()); - TS_ASSERT_EQUALS(data.str(), bigtest); - } + TS_ASSERT_THROWS_NOTHING(igzs >> &data); + TS_ASSERT(igzs); + TS_ASSERT(igzs.eof()); + TS_ASSERT_EQUALS(data.str(), bigtest); + } - //Clear and reuse buffers - data.str(""); - gzip_in.clear(); - gzip_in.seekg(0); - //Now try the same with larger buffer (but not large enough for all output, uncompressed size 1561 bytes) - { - izlibstream igzs(gzip_in, 1000); - igzs.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT(igzs.good()); + // Clear and reuse buffers + data.str(""); + gzip_in.clear(); + gzip_in.seekg(0); + // Now try the same with larger buffer (but not large enough for all + // output, uncompressed size 1561 bytes) + { + izlibstream igzs(gzip_in, 1000); + igzs.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT(igzs.good()); - TS_ASSERT_THROWS_NOTHING(igzs >> &data); - TS_ASSERT(igzs); - TS_ASSERT(igzs.eof()); - TS_ASSERT_EQUALS(data.str(), bigtest); - } + TS_ASSERT_THROWS_NOTHING(igzs >> &data); + TS_ASSERT(igzs); + TS_ASSERT(igzs.eof()); + TS_ASSERT_EQUALS(data.str(), bigtest); + } - data.str(""); - gzip_in.clear(); - gzip_in.seekg(0); - //Now with large buffer - { - izlibstream igzs(gzip_in, 4000); - igzs.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT(igzs.good()); + data.str(""); + gzip_in.clear(); + gzip_in.seekg(0); + // Now with large buffer + { + izlibstream igzs(gzip_in, 4000); + igzs.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT(igzs.good()); - TS_ASSERT_THROWS_NOTHING(igzs >> &data); - TS_ASSERT(igzs); - TS_ASSERT(igzs.eof()); - TS_ASSERT_EQUALS(data.str(), bigtest); - } - } + TS_ASSERT_THROWS_NOTHING(igzs >> &data); + TS_ASSERT(igzs); + TS_ASSERT(igzs.eof()); + TS_ASSERT_EQUALS(data.str(), bigtest); + } + } - void test_inflate_zlib() - { - std::string input(__binary_bigtest_zlib_start, __binary_bigtest_zlib_end); - std::istringstream zlib_in(input, std::ios::binary); - TS_ASSERT(zlib_in); + void test_inflate_zlib() + { + std::string input(__binary_bigtest_zlib_start, + __binary_bigtest_zlib_end); + std::istringstream zlib_in(input, std::ios::binary); + TS_ASSERT(zlib_in); - std::stringbuf data; - izlibstream izls(zlib_in, 256); - izls.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT(izls.good()); + std::stringbuf data; + izlibstream izls(zlib_in, 256); + izls.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT(izls.good()); - TS_ASSERT_THROWS_NOTHING(izls >> &data); - TS_ASSERT(izls); - TS_ASSERT(izls.eof()); - TS_ASSERT_EQUALS(data.str(), bigtest); - } + TS_ASSERT_THROWS_NOTHING(izls >> &data); + TS_ASSERT(izls); + TS_ASSERT(izls.eof()); + TS_ASSERT_EQUALS(data.str(), bigtest); + } - void test_inflate_corrupt() - { - std::string input(__binary_bigtest_corrupt_nbt_start, __binary_bigtest_corrupt_nbt_end); - std::istringstream gzip_in(input, std::ios::binary); - TS_ASSERT(gzip_in); + void test_inflate_corrupt() + { + std::string input(__binary_bigtest_corrupt_nbt_start, + __binary_bigtest_corrupt_nbt_end); + std::istringstream gzip_in(input, std::ios::binary); + TS_ASSERT(gzip_in); - std::vector buf(bigtest.size()); - izlibstream igzs(gzip_in); - igzs.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT_THROWS(igzs.read(buf.data(), buf.size()), zlib_error); - TS_ASSERT(igzs.bad()); - } + std::vector buf(bigtest.size()); + izlibstream igzs(gzip_in); + igzs.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT_THROWS(igzs.read(buf.data(), buf.size()), zlib_error); + TS_ASSERT(igzs.bad()); + } - void test_inflate_eof() - { - std::string input(__binary_bigtest_eof_nbt_start, __binary_bigtest_eof_nbt_end); - std::istringstream gzip_in(input, std::ios::binary); - TS_ASSERT(gzip_in); + void test_inflate_eof() + { + std::string input(__binary_bigtest_eof_nbt_start, + __binary_bigtest_eof_nbt_end); + std::istringstream gzip_in(input, std::ios::binary); + TS_ASSERT(gzip_in); - std::vector buf(bigtest.size()); - izlibstream igzs(gzip_in); - igzs.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT_THROWS(igzs.read(buf.data(), buf.size()), zlib_error); - TS_ASSERT(igzs.bad()); - } + std::vector buf(bigtest.size()); + izlibstream igzs(gzip_in); + igzs.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT_THROWS(igzs.read(buf.data(), buf.size()), zlib_error); + TS_ASSERT(igzs.bad()); + } - void test_inflate_trailing() - { - //This file contains additional uncompressed data after the zlib-compressed data - std::string input(__binary_trailing_data_zlib_start, __binary_trailing_data_zlib_end); - std::istringstream file(input, std::ios::binary); - izlibstream izls(file, 32); - TS_ASSERT(file && izls); + void test_inflate_trailing() + { + // This file contains additional uncompressed data after the + // zlib-compressed data + std::string input(__binary_trailing_data_zlib_start, + __binary_trailing_data_zlib_end); + std::istringstream file(input, std::ios::binary); + izlibstream izls(file, 32); + TS_ASSERT(file && izls); - std::string str; - izls >> str; - TS_ASSERT(izls); - TS_ASSERT(izls.eof()); - TS_ASSERT_EQUALS(str, "foobar"); + std::string str; + izls >> str; + TS_ASSERT(izls); + TS_ASSERT(izls.eof()); + TS_ASSERT_EQUALS(str, "foobar"); - //Now read the uncompressed data - TS_ASSERT(file); - TS_ASSERT(!file.eof()); - file >> str; - TS_ASSERT(!file.bad()); - TS_ASSERT_EQUALS(str, "barbaz"); - } + // Now read the uncompressed data + TS_ASSERT(file); + TS_ASSERT(!file.eof()); + file >> str; + TS_ASSERT(!file.bad()); + TS_ASSERT_EQUALS(str, "barbaz"); + } - void test_deflate_zlib() - { - //Here we assume that inflating works and has already been tested - std::stringstream str; - std::stringbuf output; - //Small buffer - { - ozlibstream ozls(str, -1, false, 256); - ozls.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT_THROWS_NOTHING(ozls << bigtest); - TS_ASSERT(ozls.good()); - TS_ASSERT_THROWS_NOTHING(ozls.close()); - TS_ASSERT(ozls.good()); - } - TS_ASSERT(str.good()); - { - izlibstream izls(str); - TS_ASSERT_THROWS_NOTHING(izls >> &output); - TS_ASSERT(izls); - } - TS_ASSERT_EQUALS(output.str(), bigtest); + void test_deflate_zlib() + { + // Here we assume that inflating works and has already been tested + std::stringstream str; + std::stringbuf output; + // Small buffer + { + ozlibstream ozls(str, -1, false, 256); + ozls.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT_THROWS_NOTHING(ozls << bigtest); + TS_ASSERT(ozls.good()); + TS_ASSERT_THROWS_NOTHING(ozls.close()); + TS_ASSERT(ozls.good()); + } + TS_ASSERT(str.good()); + { + izlibstream izls(str); + TS_ASSERT_THROWS_NOTHING(izls >> &output); + TS_ASSERT(izls); + } + TS_ASSERT_EQUALS(output.str(), bigtest); - str.clear(); str.str(""); - output.str(""); - //Medium sized buffer - //Write first half, then flush and write second half - { - ozlibstream ozls(str, 9, false, 512); - ozls.exceptions(std::ios::failbit | std::ios::badbit); + str.clear(); + str.str(""); + output.str(""); + // Medium sized buffer + // Write first half, then flush and write second half + { + ozlibstream ozls(str, 9, false, 512); + ozls.exceptions(std::ios::failbit | std::ios::badbit); - std::string half1 = bigtest.substr(0, bigtest.size()/2); - std::string half2 = bigtest.substr(bigtest.size()/2); - TS_ASSERT_THROWS_NOTHING(ozls << half1 << std::flush << half2); - TS_ASSERT(ozls.good()); - TS_ASSERT_THROWS_NOTHING(ozls.close()); - TS_ASSERT(ozls.good()); - } - TS_ASSERT(str.good()); - { - izlibstream izls(str); - izls >> &output; - TS_ASSERT(izls); - } - TS_ASSERT_EQUALS(output.str(), bigtest); + std::string half1 = bigtest.substr(0, bigtest.size() / 2); + std::string half2 = bigtest.substr(bigtest.size() / 2); + TS_ASSERT_THROWS_NOTHING(ozls << half1 << std::flush << half2); + TS_ASSERT(ozls.good()); + TS_ASSERT_THROWS_NOTHING(ozls.close()); + TS_ASSERT(ozls.good()); + } + TS_ASSERT(str.good()); + { + izlibstream izls(str); + izls >> &output; + TS_ASSERT(izls); + } + TS_ASSERT_EQUALS(output.str(), bigtest); - str.clear(); str.str(""); - output.str(""); - //Large buffer - { - ozlibstream ozls(str, 1, false, 4000); - ozls.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT_THROWS_NOTHING(ozls << bigtest); - TS_ASSERT(ozls.good()); - TS_ASSERT_THROWS_NOTHING(ozls.close()); - TS_ASSERT_THROWS_NOTHING(ozls.close()); //closing twice shouldn't be a problem - TS_ASSERT(ozls.good()); - } - TS_ASSERT(str.good()); - { - izlibstream izls(str); - izls >> &output; - TS_ASSERT(izls); - } - TS_ASSERT_EQUALS(output.str(), bigtest); - } + str.clear(); + str.str(""); + output.str(""); + // Large buffer + { + ozlibstream ozls(str, 1, false, 4000); + ozls.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT_THROWS_NOTHING(ozls << bigtest); + TS_ASSERT(ozls.good()); + TS_ASSERT_THROWS_NOTHING(ozls.close()); + TS_ASSERT_THROWS_NOTHING( + ozls.close()); // closing twice shouldn't be a problem + TS_ASSERT(ozls.good()); + } + TS_ASSERT(str.good()); + { + izlibstream izls(str); + izls >> &output; + TS_ASSERT(izls); + } + TS_ASSERT_EQUALS(output.str(), bigtest); + } - void test_deflate_gzip() - { - std::stringstream str; - std::stringbuf output; - { - ozlibstream ozls(str, -1, true); - ozls.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT_THROWS_NOTHING(ozls << bigtest); - TS_ASSERT(ozls.good()); - TS_ASSERT_THROWS_NOTHING(ozls.close()); - TS_ASSERT(ozls.good()); - } - TS_ASSERT(str.good()); - { - izlibstream izls(str); - izls >> &output; - TS_ASSERT(izls); - } - TS_ASSERT_EQUALS(output.str(), bigtest); - } + void test_deflate_gzip() + { + std::stringstream str; + std::stringbuf output; + { + ozlibstream ozls(str, -1, true); + ozls.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT_THROWS_NOTHING(ozls << bigtest); + TS_ASSERT(ozls.good()); + TS_ASSERT_THROWS_NOTHING(ozls.close()); + TS_ASSERT(ozls.good()); + } + TS_ASSERT(str.good()); + { + izlibstream izls(str); + izls >> &output; + TS_ASSERT(izls); + } + TS_ASSERT_EQUALS(output.str(), bigtest); + } - void test_deflate_closed() - { - std::stringstream str; - { - ozlibstream ozls(str); - ozls.exceptions(std::ios::failbit | std::ios::badbit); - TS_ASSERT_THROWS_NOTHING(ozls << bigtest); - TS_ASSERT_THROWS_NOTHING(ozls.close()); - TS_ASSERT_THROWS_NOTHING(ozls << "foo"); - TS_ASSERT_THROWS_ANYTHING(ozls.close()); - TS_ASSERT(ozls.bad()); - TS_ASSERT(!str); - } - str.clear(); - str.seekp(0); - { - ozlibstream ozls(str); - //this time without exceptions - TS_ASSERT_THROWS_NOTHING(ozls << bigtest); - TS_ASSERT_THROWS_NOTHING(ozls.close()); - TS_ASSERT_THROWS_NOTHING(ozls << "foo" << std::flush); - TS_ASSERT(ozls.bad()); - TS_ASSERT_THROWS_NOTHING(ozls.close()); - TS_ASSERT(ozls.bad()); - TS_ASSERT(!str); - } - } + void test_deflate_closed() + { + std::stringstream str; + { + ozlibstream ozls(str); + ozls.exceptions(std::ios::failbit | std::ios::badbit); + TS_ASSERT_THROWS_NOTHING(ozls << bigtest); + TS_ASSERT_THROWS_NOTHING(ozls.close()); + TS_ASSERT_THROWS_NOTHING(ozls << "foo"); + TS_ASSERT_THROWS_ANYTHING(ozls.close()); + TS_ASSERT(ozls.bad()); + TS_ASSERT(!str); + } + str.clear(); + str.seekp(0); + { + ozlibstream ozls(str); + // this time without exceptions + TS_ASSERT_THROWS_NOTHING(ozls << bigtest); + TS_ASSERT_THROWS_NOTHING(ozls.close()); + TS_ASSERT_THROWS_NOTHING(ozls << "foo" << std::flush); + TS_ASSERT(ozls.bad()); + TS_ASSERT_THROWS_NOTHING(ozls.close()); + TS_ASSERT(ozls.bad()); + TS_ASSERT(!str); + } + } }; -- cgit 0.0.5-2-1-g0f52