add checkCompatibility method, convertStrategy field, tests
This commit is contained in:
parent
21b2060685
commit
8baabf4c0d
@ -1,6 +1,7 @@
|
||||
#include "StructLayout.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <climits>
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
@ -48,22 +49,118 @@ static inline constexpr bool is_numeric_type(FieldType type) {
|
||||
return is_floating_point_type(type) || is_integer_type(type);
|
||||
}
|
||||
|
||||
static inline FieldIncapatibilityType checkIncapatibility(
|
||||
const Field& srcField, const Field& dstField
|
||||
) {
|
||||
auto type = FieldIncapatibilityType::NONE;
|
||||
if (dstField.elements < srcField.elements) {
|
||||
type = FieldIncapatibilityType::DATA_LOSS;
|
||||
}
|
||||
if (srcField.type == dstField.type) {
|
||||
return type;
|
||||
}
|
||||
if (is_numeric_type(srcField.type) && is_numeric_type(dstField.type)) {
|
||||
int sizediff =
|
||||
sizeof_type(dstField.type) - sizeof_type(srcField.type);
|
||||
if (sizediff < 0) {
|
||||
type = std::max(type, FieldIncapatibilityType::DATA_LOSS);
|
||||
}
|
||||
} else {
|
||||
type = std::max(type, FieldIncapatibilityType::TYPE_ERROR);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static inline integer_t clamp_value(integer_t value, FieldType type) {
|
||||
auto typesize = sizeof_type(type) * CHAR_BIT;
|
||||
integer_t minval = -(1 << (typesize-1));
|
||||
integer_t maxval = (1 << (typesize-1))-1;
|
||||
return std::min(maxval, std::max(minval, value));
|
||||
}
|
||||
|
||||
static void reset_integer(
|
||||
const StructLayout& srcLayout,
|
||||
const StructLayout& dstLayout,
|
||||
const Field& field,
|
||||
const Field& dstField,
|
||||
const ubyte* src,
|
||||
ubyte* dst
|
||||
) {
|
||||
int elements = std::min(field.elements, dstField.elements);
|
||||
for (int i = 0; i < elements; i++) {
|
||||
auto value = srcLayout.getInteger(src, field.name, i);
|
||||
auto clamped = clamp_value(value, dstField.type);
|
||||
if (dstField.convertStrategy == FieldConvertStrategy::CLAMP) {
|
||||
value = clamped;
|
||||
} else {
|
||||
if (clamped != value) {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
dstLayout.setInteger(dst, value, field.name, i);
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_number(
|
||||
const StructLayout& srcLayout,
|
||||
const StructLayout& dstLayout,
|
||||
const Field& field,
|
||||
const Field& dstField,
|
||||
const ubyte* src,
|
||||
ubyte* dst
|
||||
) {
|
||||
int elements = std::min(field.elements, dstField.elements);
|
||||
for (int i = 0; i < elements; i++) {
|
||||
auto value = srcLayout.getNumber(src, field.name, i);
|
||||
dstLayout.setNumber(dst, value, field.name, i);
|
||||
}
|
||||
}
|
||||
|
||||
void StructLayout::convert(
|
||||
const StructLayout& srcLayout,
|
||||
const ubyte* src,
|
||||
ubyte* dst,
|
||||
bool allowDataLoss
|
||||
) const {
|
||||
for (const Field& field : fields) {
|
||||
auto srcField = srcLayout.getField(field.name);
|
||||
if (srcField == nullptr) {
|
||||
std::memset(dst + field.offset, 0, field.size);
|
||||
std::memset(dst, 0, totalSize);
|
||||
for (const Field& field : srcLayout.fields) {
|
||||
auto dstField = getField(field.name);
|
||||
if (dstField == nullptr) {
|
||||
continue;
|
||||
}
|
||||
// TODO: implement
|
||||
auto type = checkIncapatibility(field, *dstField);
|
||||
if (type == FieldIncapatibilityType::TYPE_ERROR) {
|
||||
continue;
|
||||
}
|
||||
// can't just memcpy, because field type may be changed without data loss
|
||||
if (is_integer_type(field.type) ||
|
||||
(is_floating_point_type(field.type) &&
|
||||
is_integer_type(dstField->type))) {
|
||||
reset_integer(srcLayout, *this, field, *dstField, src, dst);
|
||||
} else if (is_floating_point_type(dstField->type)) {
|
||||
reset_number(srcLayout, *this, field, *dstField, src, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<FieldIncapatibility> StructLayout::checkCompatibility(
|
||||
const StructLayout& dstLayout
|
||||
) {
|
||||
std::vector<FieldIncapatibility> report;
|
||||
for (const Field& field : fields) {
|
||||
auto dstField = dstLayout.getField(field.name);
|
||||
if (dstField == nullptr) {
|
||||
report.push_back({field.name, FieldIncapatibilityType::MISSING});
|
||||
continue;
|
||||
}
|
||||
auto type = checkIncapatibility(field, *dstField);
|
||||
if (type != FieldIncapatibilityType::NONE) {
|
||||
report.push_back({field.name, type});
|
||||
}
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
const Field& StructLayout::requreField(const std::string& name) const {
|
||||
auto found = indices.find(name);
|
||||
if (found == indices.end()) {
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
|
||||
@ -13,6 +14,19 @@ namespace data {
|
||||
COUNT
|
||||
};
|
||||
|
||||
/// @brief Sorted by severity
|
||||
enum class FieldIncapatibilityType {
|
||||
NONE = 0,
|
||||
DATA_LOSS,
|
||||
TYPE_ERROR,
|
||||
MISSING,
|
||||
};
|
||||
|
||||
struct FieldIncapatibility {
|
||||
std::string name;
|
||||
FieldIncapatibilityType type;
|
||||
};
|
||||
|
||||
inline constexpr int sizeof_type(FieldType type) {
|
||||
const int sizes[static_cast<int>(FieldType::COUNT)] = {
|
||||
1, 2, 4, 8, 4, 8, 1
|
||||
@ -25,11 +39,21 @@ namespace data {
|
||||
dataloss_error(const std::string& message) : std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
/// @brief Strategy will be used if value can't be left the same on conversion
|
||||
enum class FieldConvertStrategy {
|
||||
/// @brief Reset field value
|
||||
RESET,
|
||||
/// @brief Clamp field value if out of type range
|
||||
CLAMP
|
||||
};
|
||||
|
||||
struct Field {
|
||||
FieldType type;
|
||||
std::string name;
|
||||
/// @brief Number of field elements (array size)
|
||||
int elements;
|
||||
/// @brief Strategy will be used in data loss case
|
||||
FieldConvertStrategy convertStrategy;
|
||||
/// @brief Byte offset of the field
|
||||
int offset;
|
||||
/// @brief Byte size of the field
|
||||
@ -154,7 +178,8 @@ namespace data {
|
||||
ubyte* dst,
|
||||
bool allowDataLoss) const;
|
||||
|
||||
/// TODO: add checkCompatibility method
|
||||
std::vector<FieldIncapatibility> checkCompatibility(
|
||||
const StructLayout& dstLayout);
|
||||
|
||||
[[nodiscard]]
|
||||
static StructLayout create(const std::vector<Field>& fields);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "data/StructLayout.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
|
||||
using namespace data;
|
||||
|
||||
@ -40,6 +42,73 @@ TEST(StructLayout, Unicode) {
|
||||
EXPECT_EQ(layout.getChars(buffer, "text"), std::string(u8"пр"));
|
||||
}
|
||||
|
||||
TEST(StructLayout, Convert) {
|
||||
|
||||
TEST(StructLayout, ConvertReorder) {
|
||||
ubyte src[16] {};
|
||||
std::vector<Field> srcFields {
|
||||
Field {FieldType::CHAR, "text", 5},
|
||||
Field {FieldType::F64, "pi", 1},
|
||||
};
|
||||
auto srcLayout = StructLayout::create(srcFields);
|
||||
srcLayout.setChars(src, "truth", "text");
|
||||
srcLayout.setNumber(src, 3.141592, "pi");
|
||||
|
||||
EXPECT_EQ(srcLayout.getChars(src, "text"), "truth");
|
||||
EXPECT_DOUBLE_EQ(srcLayout.getNumber(src, "pi"), 3.141592);
|
||||
|
||||
ubyte dst[32] {};
|
||||
std::vector<Field> dstFields {
|
||||
Field {FieldType::I64, "arr", 2}, // an array of 2 i64
|
||||
Field {FieldType::CHAR, "text", 5},
|
||||
Field {FieldType::F64, "pi", 1},
|
||||
};
|
||||
auto dstLayout = StructLayout::create(dstFields);
|
||||
EXPECT_EQ(srcLayout.checkCompatibility(dstLayout).size(), 0);
|
||||
dstLayout.convert(srcLayout, src, dst, false);
|
||||
|
||||
EXPECT_EQ(dstLayout.getChars(dst, "text"), "truth");
|
||||
EXPECT_DOUBLE_EQ(dstLayout.getNumber(dst, "pi"), 3.141592);
|
||||
}
|
||||
|
||||
TEST(StructLayout, ConvertWithLoss) {
|
||||
ubyte src[32] {};
|
||||
std::vector<Field> srcFields {
|
||||
Field {FieldType::CHAR, "text", 5},
|
||||
Field {FieldType::I16, "someint", 1},
|
||||
Field {FieldType::F64, "pi", 1},
|
||||
};
|
||||
auto srcLayout = StructLayout::create(srcFields);
|
||||
srcLayout.setChars(src, "truth", "text");
|
||||
srcLayout.setInteger(src, 150, "someint");
|
||||
srcLayout.setNumber(src, 3.141592, "pi");
|
||||
|
||||
EXPECT_EQ(srcLayout.getChars(src, "text"), "truth");
|
||||
EXPECT_EQ(srcLayout.getInteger(src, "someint"), 150);
|
||||
EXPECT_DOUBLE_EQ(srcLayout.getNumber(src, "pi"), 3.141592);
|
||||
|
||||
ubyte dst[8] {};
|
||||
std::vector<Field> dstFields {
|
||||
Field {FieldType::CHAR, "text", 3},
|
||||
Field {FieldType::I8, "someint", 1, FieldConvertStrategy::CLAMP},
|
||||
};
|
||||
auto dstLayout = StructLayout::create(dstFields);
|
||||
auto report = srcLayout.checkCompatibility(dstLayout);
|
||||
std::sort(report.begin(), report.end(), [](const auto& a, const auto& b) {
|
||||
return a.name < b.name;
|
||||
});
|
||||
EXPECT_EQ(report.size(), 3);
|
||||
|
||||
EXPECT_EQ(report[0].name, "pi");
|
||||
EXPECT_EQ(report[0].type, FieldIncapatibilityType::MISSING);
|
||||
|
||||
EXPECT_EQ(report[1].name, "someint");
|
||||
EXPECT_EQ(report[1].type, FieldIncapatibilityType::DATA_LOSS);
|
||||
|
||||
EXPECT_EQ(report[2].name, "text");
|
||||
EXPECT_EQ(report[2].type, FieldIncapatibilityType::DATA_LOSS);
|
||||
|
||||
// convert with losses
|
||||
dstLayout.convert(srcLayout, src, dst, true);
|
||||
|
||||
EXPECT_EQ(dstLayout.getChars(dst, "text"), "tru");
|
||||
EXPECT_EQ(dstLayout.getInteger(dst, "someint"), INT8_MAX);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user