blob: 263b8022c3f827b4678dbdab1ba3008a632bbcc1 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This header contains implementations of the types in the Emboss Prelude
// (UInt, Int, Flag, etc.)
#ifndef EMBOSS_RUNTIME_CPP_EMBOSS_PRELUDE_H_
#define EMBOSS_RUNTIME_CPP_EMBOSS_PRELUDE_H_
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <limits>
#include <type_traits>
#include <utility>
#include "runtime/cpp/emboss_cpp_util.h"
// This namespace must match the [(cpp) namespace] in the Emboss prelude.
namespace emboss {
namespace prelude {
// FlagView is the C++ implementation of the Emboss "Flag" type, which is a
// 1-bit value.
template <class Parameters, class BitBlock>
class FlagView final {
public:
static_assert(Parameters::kBits == 1, "FlagView must be 1 bit.");
explicit FlagView(BitBlock bits) : bit_block_{bits} {}
FlagView() : bit_block_() {}
FlagView(const FlagView &) = default;
FlagView(FlagView &&) = default;
FlagView &operator=(const FlagView &) = default;
FlagView &operator=(FlagView &&) = default;
~FlagView() = default;
bool Read() const {
bool result = bit_block_.ReadUInt();
EMBOSS_CHECK(Parameters::ValueIsOk(result));
return result;
}
bool UncheckedRead() const { return bit_block_.UncheckedReadUInt(); }
void Write(bool value) const {
const bool result = TryToWrite(value);
(void)result;
EMBOSS_CHECK(result);
}
bool TryToWrite(bool value) const {
if (!CouldWriteValue(value)) return false;
if (!IsComplete()) return false;
bit_block_.WriteUInt(value);
return true;
}
static constexpr bool CouldWriteValue(bool value) {
return Parameters::ValueIsOk(value);
}
void UncheckedWrite(bool value) const {
bit_block_.UncheckedWriteUInt(value);
}
template <typename OtherView>
void CopyFrom(const OtherView &other) const {
Write(other.Read());
}
template <typename OtherView>
void UncheckedCopyFrom(const OtherView &other) const {
UncheckedWrite(other.UncheckedRead());
}
template <typename OtherView>
bool TryToCopyFrom(const OtherView &other) const {
return TryToWrite(other.Read());
}
bool Ok() const {
return IsComplete() && Parameters::ValueIsOk(UncheckedRead());
}
template <class OtherBitBlock>
bool Equals(const FlagView<Parameters, OtherBitBlock> &other) const {
return Read() == other.Read();
}
template <class OtherBitBlock>
bool UncheckedEquals(const FlagView<Parameters, OtherBitBlock> &other) const {
return UncheckedRead() == other.UncheckedRead();
}
bool IsComplete() const {
return bit_block_.Ok() && bit_block_.SizeInBits() > 0;
}
template <class Stream>
bool UpdateFromTextStream(Stream *stream) const {
::std::string token;
if (!::emboss::support::ReadToken(stream, &token)) return false;
if (token == "true") {
return TryToWrite(true);
} else if (token == "false") {
return TryToWrite(false);
}
// TODO(bolms): Provide a way to get an error message on parse failure.
return false;
}
template <class Stream>
void WriteToTextStream(Stream *stream,
const ::emboss::TextOutputOptions &options) const {
::emboss::support::WriteBooleanViewToTextStream(this, stream, options);
}
private:
BitBlock bit_block_;
};
// UIntView is a view for UInts inside of bitfields.
template <class Parameters, class BitViewType>
class UIntView final {
public:
using ValueType = typename ::emboss::support::LeastWidthInteger<
Parameters::kBits>::Unsigned;
static_assert(
Parameters::kBits <= sizeof(ValueType) * 8,
"UIntView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
template <typename... Args>
explicit UIntView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
UIntView() : buffer_() {}
UIntView(const UIntView &) = default;
UIntView(UIntView &&) = default;
UIntView &operator=(const UIntView &) = default;
UIntView &operator=(UIntView &&) = default;
~UIntView() = default;
ValueType Read() const {
ValueType result = buffer_.ReadUInt();
EMBOSS_CHECK(Parameters::ValueIsOk(result));
return result;
}
ValueType UncheckedRead() const { return buffer_.UncheckedReadUInt(); }
// The Write, TryToWrite, and CouldWriteValue methods are templated in order
// to avoid surprises due to implicit narrowing.
//
// In C++, you can pass (say) an `int` to a function expecting `uint8_t`, and
// the compiler will silently cast the `int` to `uint8_t`, which can change
// the value. Even with fairly aggressive warnings, something like this will
// silently compile, and print `256 is not >= 128!`:
//
// bool is_big_uint8(uint8_t value) { return value >= 128; }
// bool is_big(uint32_t value) { return is_big_uint8(value); }
// int main() {
// assert(!is_big(256)); // big is truncated to 0.
// std::cout << 256 << " is not >= 128!\n";
// return 0;
// }
//
// (Most compilers will give a warning when directly passing a *constant* that
// gets truncated; for example, GCC will throw -Woverflow on
// `is_big_uint8(256U)`.)
template <typename IntT,
typename = typename ::std::enable_if<
(::std::numeric_limits<typename ::std::remove_cv<
typename ::std::remove_reference<IntT>::type>::type>::
is_integer &&
!::std::is_same<bool, typename ::std::remove_cv<
typename ::std::remove_reference<
IntT>::type>::type>::value) ||
::std::is_enum<IntT>::value>::type>
void Write(IntT value) const {
const bool result = TryToWrite(value);
(void)result;
EMBOSS_CHECK(result);
}
template <typename IntT,
typename = typename ::std::enable_if<
(::std::numeric_limits<typename ::std::remove_cv<
typename ::std::remove_reference<IntT>::type>::type>::
is_integer &&
!::std::is_same<bool, typename ::std::remove_cv<
typename ::std::remove_reference<
IntT>::type>::type>::value) ||
::std::is_enum<IntT>::value>::type>
bool TryToWrite(IntT value) const {
if (!CouldWriteValue(value)) return false;
if (!IsComplete()) return false;
buffer_.WriteUInt(value);
return true;
}
template <typename IntT,
typename = typename ::std::enable_if<
(::std::numeric_limits<typename ::std::remove_cv<
typename ::std::remove_reference<IntT>::type>::type>::
is_integer &&
!::std::is_same<bool, typename ::std::remove_cv<
typename ::std::remove_reference<
IntT>::type>::type>::value) ||
::std::is_enum<IntT>::value>::type>
static constexpr bool CouldWriteValue(IntT value) {
// Implicit conversions are doing some work here, but the upshot is that the
// value must be at least 0, and at most (2**kBits)-1. The clause to
// compute (2**kBits)-1 should not be "simplified" further.
//
// Because of C++ implicit integer promotions, the (2**kBits)-1 computation
// works differently when `ValueType` is smaller than `unsigned int` than it
// does when `ValueType` is at least as big as `unsigned int`.
//
// For example, when `ValueType` is `uint8_t` and `kBits` is 8:
//
// 1. `static_cast<ValueType>(1)` becomes `uint8_t(1)`.
// 2. `uint8_t(1) << (kBits - 1)` is `uint8_t(1) << 7`.
// 3. The shift operator `<<` promotes its left operand to `unsigned`,
// giving `unsigned(1) << 7`.
// 4. `unsigned(1) << 7` becomes `unsigned(0x80)`.
// 5. `unsigned(0x80) << 1` becomes `unsigned(0x100)`.
// 6. Finally, `unsigned(0x100) - 1` is `unsigned(0xff)`.
//
// (Note that the cases where `kBits` is less than `sizeof(ValueType) * 8`
// are very similar.)
//
// When `ValueType` is `uint32_t`, `unsigned` is 32 bits, and `kBits` is 32:
//
// 1. `static_cast<ValueType>(1)` becomes `uint32_t(1)`.
// 2. `uint32_t(1) << (kBits - 1)` is `uint32_t(1) << 31`.
// 3. The shift operator `<<` does *not* further promote `uint32_t`.
// 4. `uint32_t(1) << 31` becomes `uint32_t(0x80000000)`. Note that
// `uint32_t(1) << 32` would be undefined behavior (shift of >= the
// size of the left operand type), which is why the shift is broken
// into two parts.
// 5. `uint32_t(0x80000000) << 1` overflows, leaving `uint32_t(0)`.
// 6. `uint32_t(0) - 1` underflows, leaving `uint32_t(0xffffffff)`.
//
// Because unsigned overflow and underflow are defined to be modulo 2**N,
// where N is the number of bits in the type, this is entirely
// standards-compliant.
return value >= 0 &&
static_cast</**/ ::std::uint64_t>(value) <=
((static_cast<ValueType>(1) << (Parameters::kBits - 1)) << 1) -
1 &&
Parameters::ValueIsOk(value);
}
void UncheckedWrite(ValueType value) const {
buffer_.UncheckedWriteUInt(value);
}
template <typename OtherView>
void CopyFrom(const OtherView &other) const {
Write(other.Read());
}
template <typename OtherView>
void UncheckedCopyFrom(const OtherView &other) const {
UncheckedWrite(other.UncheckedRead());
}
template <typename OtherView>
bool TryToCopyFrom(const OtherView &other) const {
return other.Ok() && TryToWrite(other.Read());
}
// All bit patterns in the underlying buffer are valid, so Ok() is always
// true if IsComplete() is true.
bool Ok() const {
return IsComplete() && Parameters::ValueIsOk(UncheckedRead());
}
template <class OtherBitViewType>
bool Equals(const UIntView<Parameters, OtherBitViewType> &other) const {
return Read() == other.Read();
}
template <class OtherBitViewType>
bool UncheckedEquals(
const UIntView<Parameters, OtherBitViewType> &other) const {
return UncheckedRead() == other.UncheckedRead();
}
bool IsComplete() const {
return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
}
template <class Stream>
bool UpdateFromTextStream(Stream *stream) const {
return support::ReadIntegerFromTextStream(this, stream);
}
template <class Stream>
void WriteToTextStream(Stream *stream,
::emboss::TextOutputOptions options) const {
support::WriteIntegerViewToTextStream(this, stream, options);
}
static constexpr int SizeInBits() { return Parameters::kBits; }
private:
BitViewType buffer_;
};
// IntView is a view for Ints inside of bitfields.
template <class Parameters, class BitViewType>
class IntView final {
public:
using ValueType =
typename ::emboss::support::LeastWidthInteger<Parameters::kBits>::Signed;
static_assert(Parameters::kBits <= sizeof(ValueType) * 8,
"IntView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
template <typename... Args>
explicit IntView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
IntView() : buffer_() {}
IntView(const IntView &) = default;
IntView(IntView &&) = default;
IntView &operator=(const IntView &) = default;
IntView &operator=(IntView &&) = default;
~IntView() = default;
ValueType Read() const {
ValueType value = ConvertToSigned(buffer_.ReadUInt());
EMBOSS_CHECK(Parameters::ValueIsOk(value));
return value;
}
ValueType UncheckedRead() const {
return ConvertToSigned(buffer_.UncheckedReadUInt());
}
// As with UIntView, above, Write, TryToWrite, and CouldWriteValue need to be
// templated in order to avoid surprises due to implicit narrowing
// conversions.
template <typename IntT,
typename = typename ::std::enable_if<
(::std::numeric_limits<typename ::std::remove_cv<
typename ::std::remove_reference<IntT>::type>::type>::
is_integer &&
!::std::is_same<bool, typename ::std::remove_cv<
typename ::std::remove_reference<
IntT>::type>::type>::value) ||
::std::is_enum<IntT>::value>::type>
void Write(IntT value) const {
const bool result = TryToWrite(value);
(void)result;
EMBOSS_CHECK(result);
}
template <typename IntT,
typename = typename ::std::enable_if<
(::std::numeric_limits<typename ::std::remove_cv<
typename ::std::remove_reference<IntT>::type>::type>::
is_integer &&
!::std::is_same<bool, typename ::std::remove_cv<
typename ::std::remove_reference<
IntT>::type>::type>::value) ||
::std::is_enum<IntT>::value>::type>
bool TryToWrite(IntT value) const {
if (!CouldWriteValue(value)) return false;
if (!IsComplete()) return false;
buffer_.WriteUInt(::emboss::support::MaskToNBits(
static_cast<typename BitViewType::ValueType>(value),
Parameters::kBits));
return true;
}
template <typename IntT,
typename = typename ::std::enable_if<
(::std::numeric_limits<typename ::std::remove_cv<
typename ::std::remove_reference<IntT>::type>::type>::
is_integer &&
!::std::is_same<bool, typename ::std::remove_cv<
typename ::std::remove_reference<
IntT>::type>::type>::value) ||
::std::is_enum<IntT>::value>::type>
static constexpr bool CouldWriteValue(IntT value) {
// This effectively checks that value >= -(2**(kBits-1) and value <=
// (2**(kBits-1))-1.
//
// This has to be done somewhat piecemeal, in order to avoid various bits of
// undefined and implementation-defined behavior.
//
// First, if IntT is an unsigned type, the check that value >=
// -(2**(kBits-1)) is skipped, in order to avoid any signed <-> unsigned
// conversions.
//
// Second, if kBits is 1, then the limits -1 and 0 are explicit, so that
// there is never a shift by -1 (which is undefined behavior).
//
// Third, the shifts are by (kBits - 2), so that they do not alter sign
// bits. To get the final bounds, we use a bit of addition and
// multiplication. For example, for 8 bits, the lower bound is (1 << 6) *
// -2, which is 64 * -2, which is -128. The corresponding upper bound is
// ((1 << 6) - 1) * 2 + 1, which is (64 - 1) * 2 + 1, which is 63 * 2 + 1,
// which is 126 + 1, which is 127. The upper bound must be computed in
// multiple steps like this in order to avoid overflow.
return (!::std::is_signed<typename ::std::remove_cv<
typename ::std::remove_reference<IntT>::type>::type>::value ||
static_cast</**/ ::std::int64_t>(value) >=
(Parameters::kBits == 1
? -1
: (static_cast<ValueType>(1) << (Parameters::kBits - 2)) *
-2)) &&
value <=
(Parameters::kBits == 1
? 0
: ((static_cast<ValueType>(1) << (Parameters::kBits - 2)) -
1) * 2 +
1) &&
Parameters::ValueIsOk(value);
}
void UncheckedWrite(ValueType value) const {
buffer_.UncheckedWriteUInt(::emboss::support::MaskToNBits(
static_cast<typename BitViewType::ValueType>(value),
Parameters::kBits));
}
template <typename OtherView>
void CopyFrom(const OtherView &other) const {
Write(other.Read());
}
template <typename OtherView>
void UncheckedCopyFrom(const IntView &other) const {
UncheckedWrite(other.UncheckedRead());
}
template <typename OtherView>
bool TryToCopyFrom(const OtherView &other) const {
return other.Ok() && TryToWrite(other.Read());
}
// All bit patterns in the underlying buffer are valid, so Ok() is always
// true if IsComplete() is true.
bool Ok() const {
return IsComplete() && Parameters::ValueIsOk(UncheckedRead());
}
template <class OtherBitViewType>
bool Equals(const IntView<Parameters, OtherBitViewType> &other) const {
return Read() == other.Read();
}
template <class OtherBitViewType>
bool UncheckedEquals(
const IntView<Parameters, OtherBitViewType> &other) const {
return UncheckedRead() == other.UncheckedRead();
}
bool IsComplete() const {
return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
}
template <class Stream>
bool UpdateFromTextStream(Stream *stream) const {
return support::ReadIntegerFromTextStream(this, stream);
}
template <class Stream>
void WriteToTextStream(Stream *stream,
::emboss::TextOutputOptions options) const {
support::WriteIntegerViewToTextStream(this, stream, options);
}
static constexpr int SizeInBits() { return Parameters::kBits; }
private:
static ValueType ConvertToSigned(typename BitViewType::ValueType data) {
static_assert(sizeof(ValueType) <= sizeof(typename BitViewType::ValueType),
"Integer types wider than BitViewType::ValueType are not "
"supported.");
#if EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT
// static_cast from unsigned to signed is implementation-defined when the
// value does not fit in the signed type (in this case, when the final value
// should be negative). Most implementations use a reasonable definition,
// so on most systems we can just cast.
//
// If the integer does not take up the full width of ValueType, it needs to
// be sign-extended until it does. The easiest way to do this is to shift
// until the sign bit is in the topmost position, then cast to signed, then
// shift back. The shift back will copy the sign bit.
return static_cast<ValueType>(
data << (sizeof(ValueType) * 8 - Parameters::kBits)) >>
(sizeof(ValueType) * 8 - Parameters::kBits);
#else
// Otherwise, in order to convert without running into
// implementation-defined behavior, first mask out the sign bit. This
// results in (final result MOD 2 ** (width of int in bits - 1)). That
// value can be safely converted to the signed ValueType.
//
// Finally, if the sign bit was set, subtract (2 ** (width of int in bits -
// 2)) twice.
//
// The 1-bit signed integer case must be handled separately, but it is
// (fortunately) quite easy to enumerate both possible values.
if (Parameters::kBits == 1) {
if (data == 0) {
return 0;
} else if (data == 1) {
return -1;
} else {
EMBOSS_CHECK(false);
return -1; // Return value if EMBOSS_CHECK is disabled.
}
} else {
typename BitViewType::ValueType sign_bit =
static_cast<typename BitViewType::ValueType>(1)
<< (Parameters::kBits - 1);
typename BitViewType::ValueType mask = sign_bit - 1;
typename BitViewType::ValueType data_mod2_to_n = mask & data;
ValueType result_sign_bit =
static_cast<ValueType>((data & sign_bit) >> 1);
return data_mod2_to_n - result_sign_bit - result_sign_bit;
}
#endif
}
BitViewType buffer_;
};
// The maximum Binary-Coded Decimal (BCD) value that fits in a particular number
// of bits.
template <typename ValueType>
constexpr inline ValueType MaxBcd(int bits) {
return bits < 4 ? (1 << bits) - 1
: 10 * (MaxBcd<ValueType>(bits - 4) + 1) - 1;
}
template <typename ValueType>
inline bool IsBcd(ValueType x) {
// Adapted from:
// https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord
//
// This determines if any nibble has a value greater than 9. It does
// this by treating operations on the n-bit value as parallel operations
// on n/4 4-bit values.
//
// The result is computed in the high bit of each nibble: if any of those
// bits is set in the end, then at least one nibble had a value in the
// range 10-15.
//
// The first check is subtle: ~x is equivalent to (nibble = 15 - nibble).
// Then, 6 is subtracted from each nibble. This operation will underflow
// if the original value was more than 9, leaving the high bit of the
// nibble set. It will also leave the high bit of the nibble set
// (without underflow) if the original value was 0 or 1.
//
// The second check is just x: the high bit of each nibble in x is set if
// that nibble's value is 8-15.
//
// Thus, the first check leaves the high bit set in any nibble with the
// value 0, 1, or 10-15, and the second check leaves the high bit set in
// any nibble with the value 8-15. Bitwise-anding these results, high
// bits are only set if the original value was 10-15.
//
// The underflow condition in the first check can screw up the condition
// for nibbles in higher positions than the underflowing nibble. This
// cannot affect the overall boolean result, because the underflow
// condition only happens if a nibble was greater than 9, and therefore
// *that* nibble's final value will be nonzero, and therefore the whole
// result will be nonzero, no matter what happens in the higher-order
// nibbles.
//
// A couple of examples in 16 bit:
//
// x = 0x09a8
// (~0x09a8 - 0x6666) & 0x09a8 & 0x8888
// ( 0xf657 - 0x6666) & 0x09a8 & 0x8888
// 0x8ff1 & 0x09a8 & 0x8888
// 0x09a0 & 0x8888
// 0x0880 Note the underflow into nibble 2
//
// x = 0x1289
// (~0x1289 - 0x6666) & 0x1289 & 0x8888
// ( 0xed76 - 0x6666) & 0x1289 & 0x8888
// 0x8710 & 0x1289 & 0x8888
// 0x0200 & 0x8888
// 0x0000
static_assert(!::std::is_signed<ValueType>::value,
"IsBcd only works on unsigned values.");
if (sizeof(ValueType) < sizeof(unsigned)) {
// For types with lower integer conversion rank than unsigned int, integer
// promotion rules cause many implicit conversions to signed int in the math
// below, which makes the math go wrong. Rather than add a dozen explicit
// casts back to ValueType, just do the math as 'unsigned'.
return IsBcd<unsigned>(x);
} else {
return ((~x - (~ValueType{0} / 0xf * 0x6 /* 0x6666...6666 */)) & x &
(~ValueType{0} / 0xf * 0x8 /* 0x8888...8888 */)) == 0;
}
}
// Base template for Binary-Coded Decimal (BCD) unsigned integer readers.
template <class Parameters, class BitViewType>
class BcdView final {
public:
using ValueType = typename ::emboss::support::LeastWidthInteger<
Parameters::kBits>::Unsigned;
static_assert(Parameters::kBits <= sizeof(ValueType) * 8,
"BcdView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
template <typename... Args>
explicit BcdView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
BcdView() : buffer_() {}
BcdView(const BcdView &) = default;
BcdView(BcdView &&) = default;
BcdView &operator=(const BcdView &) = default;
BcdView &operator=(BcdView &&) = default;
~BcdView() = default;
ValueType Read() const {
EMBOSS_CHECK(Ok());
return ConvertToBinary(buffer_.ReadUInt());
}
ValueType UncheckedRead() const {
return ConvertToBinary(buffer_.UncheckedReadUInt());
}
void Write(ValueType value) const {
const bool result = TryToWrite(value);
(void)result;
EMBOSS_CHECK(result);
}
bool TryToWrite(ValueType value) const {
if (!CouldWriteValue(value)) return false;
if (!IsComplete()) return false;
buffer_.WriteUInt(ConvertToBcd(value));
return true;
}
static constexpr bool CouldWriteValue(ValueType value) {
return value <= MaxValue() && Parameters::ValueIsOk(value);
}
void UncheckedWrite(ValueType value) const {
buffer_.UncheckedWriteUInt(ConvertToBcd(value));
}
template <class Stream>
bool UpdateFromTextStream(Stream *stream) const {
return support::ReadIntegerFromTextStream(this, stream);
}
template <class Stream>
void WriteToTextStream(Stream *stream,
::emboss::TextOutputOptions options) const {
// TODO(bolms): This shares the numeric_base() option with IntView and
// UIntView (and EnumView, for unknown enum values). It seems like an end
// user might prefer to see BCD values in decimal, even if they want to see
// values of other numeric types in hex or binary. It seems like there
// could be some fancy C++ trickery to allow separate options for separate
// view types.
support::WriteIntegerViewToTextStream(this, stream, options);
}
template <typename OtherView>
void CopyFrom(const OtherView &other) const {
Write(other.Read());
}
template <typename OtherView>
void UncheckedCopyFrom(const OtherView &other) const {
UncheckedWrite(other.UncheckedRead());
}
template <typename OtherView>
bool TryToCopyFrom(const OtherView &other) const {
return other.Ok() && TryToWrite(other.Read());
}
bool Ok() const {
if (!IsComplete()) return false;
if (!IsBcd(buffer_.ReadUInt())) return false;
if (!Parameters::ValueIsOk(UncheckedRead())) return false;
return true;
}
template <class OtherBitViewType>
bool Equals(const BcdView<Parameters, OtherBitViewType> &other) const {
return Read() == other.Read();
}
template <class OtherBitViewType>
bool UncheckedEquals(
const BcdView<Parameters, OtherBitViewType> &other) const {
return UncheckedRead() == other.UncheckedRead();
}
bool IsComplete() const {
return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
}
static constexpr int SizeInBits() { return Parameters::kBits; }
private:
static ValueType ConvertToBinary(ValueType bcd_value) {
ValueType result = 0;
ValueType multiplier = 1;
for (int shift = 0; shift < Parameters::kBits; shift += 4) {
result += ((bcd_value >> shift) & 0xf) * multiplier;
multiplier *= 10;
}
return result;
}
static ValueType ConvertToBcd(ValueType value) {
ValueType bcd_value = 0;
for (int shift = 0; shift < Parameters::kBits; shift += 4) {
bcd_value |= (value % 10) << shift;
value /= 10;
}
return bcd_value;
}
static constexpr ValueType MaxValue() {
return MaxBcd<ValueType>(Parameters::kBits);
}
BitViewType buffer_;
};
// FloatView is the view for the Emboss Float type.
template <class Parameters, class BitViewType>
class FloatView final {
static_assert(Parameters::kBits == 32 || Parameters::kBits == 64,
"Only 32- and 64-bit floats are currently supported.");
public:
using ValueType = typename support::FloatType<Parameters::kBits>::Type;
template <typename... Args>
explicit FloatView(Args &&... args)
: buffer_{::std::forward<Args>(args)...} {}
FloatView() : buffer_() {}
FloatView(const FloatView &) = default;
FloatView(FloatView &&) = default;
FloatView &operator=(const FloatView &) = default;
FloatView &operator=(FloatView &&) = default;
~FloatView() = default;
ValueType Read() const { return ConvertToFloat(buffer_.ReadUInt()); }
ValueType UncheckedRead() const {
return ConvertToFloat(buffer_.UncheckedReadUInt());
}
void Write(ValueType value) const {
const bool result = TryToWrite(value);
(void)result;
EMBOSS_CHECK(result);
}
bool TryToWrite(ValueType value) const {
if (!CouldWriteValue(value)) return false;
if (!IsComplete()) return false;
buffer_.WriteUInt(ConvertToUInt(value));
return true;
}
static constexpr bool CouldWriteValue(ValueType value) { return true; }
void UncheckedWrite(ValueType value) const {
buffer_.UncheckedWriteUInt(ConvertToUInt(value));
}
template <typename OtherView>
void CopyFrom(const OtherView &other) const {
Write(other.Read());
}
template <typename OtherView>
void UncheckedCopyFrom(const OtherView &other) const {
UncheckedWrite(other.UncheckedRead());
}
template <typename OtherView>
bool TryToCopyFrom(const OtherView &other) const {
return other.Ok() && TryToWrite(other.Read());
}
// All bit patterns in the underlying buffer are valid, so Ok() is always
// true if IsComplete() is true.
bool Ok() const { return IsComplete(); }
template <class OtherBitViewType>
bool Equals(const FloatView<Parameters, OtherBitViewType> &other) const {
return Read() == other.Read();
}
template <class OtherBitViewType>
bool UncheckedEquals(
const FloatView<Parameters, OtherBitViewType> &other) const {
return UncheckedRead() == other.UncheckedRead();
}
bool IsComplete() const {
return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
}
template <class Stream>
bool UpdateFromTextStream(Stream *stream) const {
return support::ReadFloatFromTextStream(this, stream);
}
template <class Stream>
void WriteToTextStream(Stream *stream,
::emboss::TextOutputOptions options) const {
support::WriteFloatToTextStream(Read(), stream, options);
}
static constexpr int SizeInBits() { return Parameters::kBits; }
private:
using UIntType = typename support::FloatType<Parameters::kBits>::UIntType;
static ValueType ConvertToFloat(UIntType bits) {
// TODO(bolms): This method assumes a few things that are not always
// strictly true; e.g., that uint32_t and float have the same endianness.
ValueType result;
memcpy(static_cast<void *>(&result), static_cast<void *>(&bits),
sizeof result);
return result;
}
static UIntType ConvertToUInt(ValueType value) {
// TODO(bolms): This method assumes a few things that are not always
// strictly true; e.g., that uint32_t and float have the same endianness.
UIntType bits;
memcpy(static_cast<void *>(&bits), static_cast<void *>(&value),
sizeof bits);
return bits;
}
BitViewType buffer_;
};
} // namespace prelude
} // namespace emboss
#endif // EMBOSS_RUNTIME_CPP_EMBOSS_PRELUDE_H_