diff --git a/flint.yaml b/flint.yaml new file mode 100644 index 0000000..a536986 --- /dev/null +++ b/flint.yaml @@ -0,0 +1,10 @@ +io: + type: lib + path: . + +test: + type: exe + path: test + dependency: + - io + diff --git a/include/io.h b/include/io.h new file mode 100644 index 0000000..725a6a2 --- /dev/null +++ b/include/io.h @@ -0,0 +1,4 @@ +#pragma once + +#include "io/write.h" +#include "io/read.h" diff --git a/include/io/helper.h b/include/io/helper.h new file mode 100644 index 0000000..0b1c239 --- /dev/null +++ b/include/io/helper.h @@ -0,0 +1,20 @@ +#pragma once +#include + +namespace io { + template + concept NumberByte = std::is_integral_v && sizeof(T) == 1; + + template + concept Container = requires (T a) { + typename T::value_type; + typename T::size_type; + typename T::iterator; + typename T::const_iterator; + a.size(); + a.begin(); + a.end(); + a.cbegin(); + a.cend(); + } && !std::is_convertible_v; +} diff --git a/include/io/read.h b/include/io/read.h new file mode 100644 index 0000000..b7527d6 --- /dev/null +++ b/include/io/read.h @@ -0,0 +1,82 @@ +#pragma once +#include "helper.h" +#include +#include + +namespace io { + // This exists to allow for specialization and clearer errors + template + T read(std::istream&) { + static_assert(!std::is_same_v, "No known specialization of io::read for type"); + } + + template + T read(std::istream& is) { + T value = 0; + is.read(reinterpret_cast(&value), sizeof(T)); + return value; + } + + namespace { + template + T read_bytes(std::istream& is, size_t length) { + T value = 0; + for (size_t i = 0; i < length; ++i) { + value <<= 8; + value |= read(is); + } + return value; + } + } + + template requires std::is_integral_v + T read(std::istream& is) { + T value = read_bytes(is, sizeof(T)); + return value; + } + + template<> + size_t read(std::istream& is) { + size_t value = 0; + uint8_t length = io::read(is); + if (length & 0b10000000) { + value = read_bytes(is, length & 0b01111111); + } else { + value = length; + } + return value; + } + + template requires std::is_convertible_v + T read(std::istream& is, size_t length) { + std::vector t(length); + is.read(t.data(), length); + + return std::string(t.data(), length); + } + + // @todo Make this require that read(is, length) exist so we can use that as a generic entrypoint + template requires std::is_convertible_v + T read(std::istream& is) { + size_t length = io::read(is); + return read(is, length); + } + + // @todo We need to support std::array + // Extra concept that checks if the size is static + template + T read(std::istream& is, size_t length) { + // @todo Properly constuct the container + T v(length); + for (size_t i = 0; i < length; ++i) { + v[i] = io::read(is); + } + return v; + } + + template + T read(std::istream& is) { + size_t length = io::read(is); + return read(is, length); + } +} diff --git a/include/io/write.h b/include/io/write.h new file mode 100644 index 0000000..ba3c916 --- /dev/null +++ b/include/io/write.h @@ -0,0 +1,71 @@ +#pragma once +#include "helper.h" +#include +#include + +namespace io { + // This exists to allow for specialization and clearer errors + template + void write(std::ostream&, T) { + static_assert(!std::is_same_v, "No known specialization of io::write for type"); + } + + template + void write(std::ostream& os, T value) { + os.write(reinterpret_cast(&value), sizeof(T)); + } + + namespace { + template + void write_bytes(std::ostream& os, T value, size_t length) { + for (size_t i = length; i > 0; --i) { + uint8_t byte = (value >> (i-1)*8) & 0xFF; + write(os, byte); + } + } + } + + template requires std::is_integral_v + void write(std::ostream& os, T value) { + write_bytes(os, value, sizeof(T)); + } + + // Special implementation for size_t (also uint64_t, so maybe this is not that smart) + template <> + void write(std::ostream& os, size_t value) { + // Check if we need more then one byte + if (value > 0b01111111) { + // Calculate how many bytes we need to store the number + uint8_t length = 0; + auto x = value; + while (x != 0) { + x >>= 8; + length++; + } + + write(os, length | 0b10000000); + write_bytes(os, value, length); + } else { + write(os, value); + } + } + + template requires std::is_convertible_v + void write(std::ostream& os, T value, bool store_length = true) { + std::string_view s = value; + if (store_length) { + write(os, s.length()); + } + os << s; + } + + template + void write(std::ostream& os, T value, bool store_length = true) { + if (store_length) { + write(os, value.size()); + } + for (const auto& entry : value) { + write(os, entry); + } + } +} diff --git a/src/write.cpp b/src/write.cpp index bf94814..4b37887 100644 --- a/src/write.cpp +++ b/src/write.cpp @@ -1,105 +1,3 @@ -#include -#include +#include "io/write.h" -namespace io { - template - void write(std::ostream&, T) { - static_assert(!std::is_same_v, "No known specialization of io::write for type"); - } - - template - concept NumberByte = std::is_integral_v && sizeof(T) == 1; - - template - void write(std::ostream& os, T value) { - os.write(reinterpret_cast(&value), sizeof(T)); - } - - namespace { - template - void write_bytes(std::ostream& os, T value, size_t length) { - for (auto i = length; i > 0; --i) { - uint8_t byte = (value >> (i-1)*8) & 0xFF; - write(os, byte); - } - } - } - - template requires std::is_integral_v - void write(std::ostream& os, T value) { - write_bytes(os, value, sizeof(T)); - } - - // Special implementation for size_t (also uint64_t, so maybe this is not that smart) - template <> - void write(std::ostream& os, size_t value) { - // Check if we need more then one byte - if (value > 0b01111111) { - // Calculate how many bytes we need to store the number - uint8_t length = 0; - auto x = value; - while (x != 0) { - x >>= 8; - length++; - } - - write(os, length | 0b10000000); - write_bytes(os, value, length); - } else { - write(os, value); - } - } - - template requires std::is_convertible_v - void write(std::ostream& os, T value) { - std::string_view s = value; - write(os, s.length()); - os << s; - } - - template - concept Container = requires (T a) { - typename T::value_type; - typename T::size_type; - typename T::iterator; - typename T::const_iterator; - a.size(); - a.begin(); - a.end(); - a.cbegin(); - a.cend(); - } && !std::is_convertible_v; - - template - void write(std::ostream& os, T value) { - write(os, value.size()); - for (const auto& entry : value) { - write(os, entry); - } - } -} - -#include -#include - -struct A { - int b; -}; - -namespace io { - template <> - void write(std::ostream& os, A value) { - write(os, value.b); - } -} - -int main() { - std::cout << "TEST\n"; - io::write(std::cout, 50); - - std::array a = {'a', 'b'}; - io::write(std::cout, a); - io::write(std::cout, "test"); - io::write(std::cout, A{4}); - std::cout << '\n'; -} +// We do not implement anything without templates diff --git a/test b/test deleted file mode 100755 index 92c840f..0000000 Binary files a/test and /dev/null differ diff --git a/test/src/test.cpp b/test/src/test.cpp new file mode 100644 index 0000000..69c3df8 --- /dev/null +++ b/test/src/test.cpp @@ -0,0 +1,61 @@ +#include + +#include +#include +#include + +struct A { + int b; +}; + +namespace io { + template <> + void write(std::ostream& os, A value) { + write(os, value.b); + } + + template <> + A read(std::istream& is) { + return A{read(is)}; + } +} + +int main() { + { + std::ofstream f("test.bin"); + io::write(f, 80085); + io::write(f, 123456); + + io::write(f, "test"); + + std::array a = {'a', 'b'}; + io::write(f, a); + + std::array b = {"Hello", "World"}; + io::write(f, b); + + io::write(f, A{4}); + } + + { + std::ifstream f("test.bin"); + std::cout << io::read(f) << '\n'; + std::cout << io::read(f) << '\n'; + std::cout << io::read(f) << '\n'; + + // auto v = io::read>(f); + auto a = io::read>(f); + for (auto& v : a) { + std::cout << v << ' '; + } + std::cout << '\n'; + + auto b = io::read>(f); + for (auto& v : b) { + std::cout << v << ' '; + } + std::cout << '\n'; + + std::cout << io::read(f).b << '\n'; + } +}