Moved write to header, added read, seperated test and started using flint
This commit is contained in:
4
include/io.h
Normal file
4
include/io.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include "io/write.h"
|
||||
#include "io/read.h"
|
||||
20
include/io/helper.h
Normal file
20
include/io/helper.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
|
||||
namespace io {
|
||||
template <typename T>
|
||||
concept NumberByte = std::is_integral_v<T> && sizeof(T) == 1;
|
||||
|
||||
template <typename T>
|
||||
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<T, std::string_view>;
|
||||
}
|
||||
82
include/io/read.h
Normal file
82
include/io/read.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
#include "helper.h"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace io {
|
||||
// This exists to allow for specialization and clearer errors
|
||||
template <typename T>
|
||||
T read(std::istream&) {
|
||||
static_assert(!std::is_same_v<T,T>, "No known specialization of io::read for type");
|
||||
}
|
||||
|
||||
template <NumberByte T>
|
||||
T read(std::istream& is) {
|
||||
T value = 0;
|
||||
is.read(reinterpret_cast<char*>(&value), sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
T read_bytes(std::istream& is, size_t length) {
|
||||
T value = 0;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
value <<= 8;
|
||||
value |= read<uint8_t>(is);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> requires std::is_integral_v<T>
|
||||
T read(std::istream& is) {
|
||||
T value = read_bytes<T>(is, sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
template<>
|
||||
size_t read(std::istream& is) {
|
||||
size_t value = 0;
|
||||
uint8_t length = io::read<uint8_t>(is);
|
||||
if (length & 0b10000000) {
|
||||
value = read_bytes<size_t>(is, length & 0b01111111);
|
||||
} else {
|
||||
value = length;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T> requires std::is_convertible_v<std::string, T>
|
||||
T read(std::istream& is, size_t length) {
|
||||
std::vector<char> t(length);
|
||||
is.read(t.data(), length);
|
||||
|
||||
return std::string(t.data(), length);
|
||||
}
|
||||
|
||||
// @todo Make this require that read<T>(is, length) exist so we can use that as a generic entrypoint
|
||||
template <typename T> requires std::is_convertible_v<std::string, T>
|
||||
T read(std::istream& is) {
|
||||
size_t length = io::read<size_t>(is);
|
||||
return read<T>(is, length);
|
||||
}
|
||||
|
||||
// @todo We need to support std::array
|
||||
// Extra concept that checks if the size is static
|
||||
template <Container T>
|
||||
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<typename T::value_type>(is);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
template <Container T>
|
||||
T read(std::istream& is) {
|
||||
size_t length = io::read<size_t>(is);
|
||||
return read<T>(is, length);
|
||||
}
|
||||
}
|
||||
71
include/io/write.h
Normal file
71
include/io/write.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "helper.h"
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace io {
|
||||
// This exists to allow for specialization and clearer errors
|
||||
template <typename T>
|
||||
void write(std::ostream&, T) {
|
||||
static_assert(!std::is_same_v<T,T>, "No known specialization of io::write for type");
|
||||
}
|
||||
|
||||
template <NumberByte T>
|
||||
void write(std::ostream& os, T value) {
|
||||
os.write(reinterpret_cast<const char*>(&value), sizeof(T));
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
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<uint8_t>(os, byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> requires std::is_integral_v<T>
|
||||
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<uint8_t>(os, length | 0b10000000);
|
||||
write_bytes(os, value, length);
|
||||
} else {
|
||||
write<uint8_t>(os, value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> requires std::is_convertible_v<T, std::string_view>
|
||||
void write(std::ostream& os, T value, bool store_length = true) {
|
||||
std::string_view s = value;
|
||||
if (store_length) {
|
||||
write<size_t>(os, s.length());
|
||||
}
|
||||
os << s;
|
||||
}
|
||||
|
||||
template <Container T>
|
||||
void write(std::ostream& os, T value, bool store_length = true) {
|
||||
if (store_length) {
|
||||
write<size_t>(os, value.size());
|
||||
}
|
||||
for (const auto& entry : value) {
|
||||
write<typename T::value_type>(os, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user