Initial open-source commit of Emboss.
diff --git a/public/BUILD b/public/BUILD new file mode 100644 index 0000000..45810a9 --- /dev/null +++ b/public/BUILD
@@ -0,0 +1,169 @@ +# 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. + +# Emboss public definitions. + +load( + ":build_defs.bzl", + "emboss_cc_util_test", +) + +py_library( + name = "ir_pb2", + srcs = ["ir_pb2.py"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "cpp_utils", + hdrs = [ + "emboss_arithmetic.h", + "emboss_array_view.h", + "emboss_bit_util.h", + "emboss_constant_view.h", + "emboss_cpp_types.h", + "emboss_cpp_util.h", + "emboss_defines.h", + "emboss_enum_view.h", + "emboss_maybe.h", + "emboss_memory_util.h", + "emboss_prelude.h", + "emboss_text_util.h", + "emboss_view_parameters.h", + ], + deps = [ + ], + visibility = ["//visibility:public"], +) + +emboss_cc_util_test( + name = "emboss_prelude_test", + srcs = [ + "emboss_prelude_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +) + +emboss_cc_util_test( + name = "emboss_arithmetic_test", + srcs = [ + "emboss_arithmetic_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +) + +emboss_cc_util_test( + name = "emboss_array_view_test", + srcs = [ + "emboss_array_view_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + "@com_google_absl//absl/strings:str_format", + ], +) + +emboss_cc_util_test( + name = "emboss_bit_util_test", + srcs = [ + "emboss_bit_util_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +) + +emboss_cc_util_test( + name = "emboss_constant_view_test", + srcs = [ + "emboss_constant_view_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +) + +emboss_cc_util_test( + name = "emboss_cpp_types_test", + srcs = [ + "emboss_cpp_types_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +) + +emboss_cc_util_test( + name = "emboss_defines_test", + srcs = [ + "emboss_defines_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +) + +emboss_cc_util_test( + name = "emboss_maybe_test", + srcs = [ + "emboss_maybe_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +) + +emboss_cc_util_test( + name = "emboss_memory_util_test", + srcs = [ + "emboss_memory_util_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +) + +emboss_cc_util_test( + name = "emboss_text_util_test", + srcs = [ + "emboss_text_util_test.cc", + ], + copts = ["-DEMBOSS_FORCE_ALL_CHECKS"], + deps = [ + ":cpp_utils", + "@com_google_googletest//:gtest_main", + ], +)
diff --git a/public/build_defs.bzl b/public/build_defs.bzl new file mode 100644 index 0000000..47ed4e1 --- /dev/null +++ b/public/build_defs.bzl
@@ -0,0 +1,93 @@ +# 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. + +# -*- mode: python; -*- +# vim:set ft=blazebuild: +"""Build defs for Emboss. + +This file exports the emboss_cc_library rule, which accepts an .emb file and +produces a corresponding C++ library. +""" + +def emboss_cc_library(name, srcs, deps = [], visibility = None): + """Constructs a C++ library from an .emb file.""" + if len(srcs) != 1: + fail( + "Must specify exactly one Emboss source file for emboss_cc_library.", + "srcs", + ) + + native.filegroup( + # The original .emb file must be visible to any other emboss_cc_library + # that specifies this emboss_cc_library in its deps. This rule makes the + # original .emb available to dependent rules. + # TODO(bolms): As an optimization, use the precompiled IR instead of + # reparsing the raw .embs. + name = name + "__emb", + srcs = srcs, + visibility = visibility, + ) + + native.genrule( + # The generated header may be used in non-cc_library rules. + name = name + "_header", + tools = [ + # TODO(bolms): Make "emboss" driver program. + "//front_end:emboss_front_end", + "//back_end/cpp:emboss_codegen_cpp", + ], + srcs = srcs + [dep + "__emb" for dep in deps], + cmd = ("$(location //front_end:emboss_front_end) " + + "--output-ir-to-stdout " + + "--import-dir=. " + + "--import-dir='$(GENDIR)' " + + "$(location {}) > $(@D)/$$(basename $(OUTS) .h).ir; " + + "$(location //back_end/cpp:emboss_codegen_cpp) " + + "< $(@D)/$$(basename $(OUTS) .h).ir > " + + "$(OUTS); " + + "rm $(@D)/$$(basename $(OUTS) .h).ir").format(") $location( ".join(srcs)), + outs = [src + ".h" for src in srcs], + # This rule should only be visible to the following rule. + visibility = ["//visibility:private"], + ) + + native.cc_library( + name = name, + hdrs = [ + ":" + name + "_header", + ], + deps = deps + [ + "//public:cpp_utils", + ], + visibility = visibility, + ) + +# TODO(bolms): Maybe move this to a non-public build_defs? +def emboss_cc_util_test(name, copts = [], **kwargs): + """Constructs two cc_test targets, with and without optimizations.""" + native.cc_test( + name = name, + copts = copts, + **kwargs + ) + native.cc_test( + name = name + "_no_opts", + copts = copts + [ + # This is generally a dangerous flag for an individual target, but + # these tests do not depend on any other .cc files that might + # #include any Emboss headers. + "-DEMBOSS_NO_OPTIMIZATIONS", + ], + **kwargs + )
diff --git a/public/emboss_arithmetic.h b/public/emboss_arithmetic.h new file mode 100644 index 0000000..bb73b7f --- /dev/null +++ b/public/emboss_arithmetic.h
@@ -0,0 +1,325 @@ +// 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. + +// Implementations for the operations and builtin functions in the Emboss +// expression language. +#ifndef EMBOSS_PUBLIC_EMBOSS_ARITHMETIC_H_ +#define EMBOSS_PUBLIC_EMBOSS_ARITHMETIC_H_ + +#include <cstdint> +#include <type_traits> + +#include "public/emboss_bit_util.h" +#include "public/emboss_maybe.h" + +namespace emboss { +namespace support { + +// Arithmetic operations +// +// Emboss arithmetic is performed by special-purpose functions, not (directly) +// using C++ operators. This allows Emboss to handle the minor differences +// between the ways that Emboss operations are defined and the way that C++ +// operations are defined, and provides a convenient way to handle arithmetic on +// values that might not be readable. +// +// The biggest differences are: +// +// Emboss's And and Or are defined to return false or true, respectively, if at +// least one operand is false or true, respectively, even if the other operand +// is not Known(). This is similar to C/C++ shortcut evaluation, except that it +// is symmetric. +// +// Emboss's expression type system uses (notionally) infinite-size integers, but +// it is an error in Emboss if the full range of any subexpression cannot fit in +// either [-(2**63), 2**63 - 1] or [0, 2**64 - 1]. Additionally, either all +// arguments to and the return type of an operation, if integers, must fit in +// int64_t, or they must all fit in uin64_t. This means that C++ integer types +// can be used directly for each operation, but casting may be required in +// between operations. + +inline constexpr bool AllKnown() { return true; } + +template <typename T, typename... RestT> +inline constexpr bool AllKnown(T value, RestT... rest) { + return value.Known() && AllKnown(rest...); +} + +// MaybeDo implements the logic of checking for known values, unwrapping the +// known values, passing the unwrapped values to OperatorT, and then rewrapping +// the result. +template <typename IntermediateT, typename ResultT, typename OperatorT, + typename... ArgsT> +inline constexpr Maybe<ResultT> MaybeDo(Maybe<ArgsT>... args) { + return AllKnown(args...) + ? Maybe<ResultT>(static_cast<ResultT>(OperatorT::template Do( + static_cast<IntermediateT>(args.ValueOrDefault())...))) + : Maybe<ResultT>(); +} + +//// Operations intended to be passed to MaybeDo: + +struct SumOperation { + template <typename T> + static inline constexpr T Do(T l, T r) { + return l + r; + } +}; + +struct DifferenceOperation { + template <typename T> + static inline constexpr T Do(T l, T r) { + return l - r; + } +}; + +struct ProductOperation { + template <typename T> + static inline constexpr T Do(T l, T r) { + return l * r; + } +}; + +// Assertions for the template types of comparisons. +template <typename ResultT, typename LeftT, typename RightT> +inline constexpr bool AssertComparisonInPartsTypes() { + static_assert(::std::is_same<ResultT, bool>::value, + "EMBOSS BUG: Comparisons must return bool."); + static_assert( + ::std::is_signed<LeftT>::value || ::std::is_signed<RightT>::value, + "EMBOSS BUG: Comparisons in parts expect one side to be signed."); + static_assert( + ::std::is_unsigned<LeftT>::value || ::std::is_unsigned<RightT>::value, + "EMBOSS BUG: Comparisons in parts expect one side to be unsigned."); + return true; // A literal return type is required for a constexpr function. +} + +struct EqualOperation { + template <typename T> + static inline constexpr bool Do(T l, T r) { + return l == r; + } +}; + +struct NotEqualOperation { + template <typename T> + static inline constexpr bool Do(T l, T r) { + return l != r; + } +}; + +struct LessThanOperation { + template <typename T> + static inline constexpr bool Do(T l, T r) { + return l < r; + } +}; + +struct LessThanOrEqualOperation { + template <typename T> + static inline constexpr bool Do(T l, T r) { + return l <= r; + } +}; + +struct GreaterThanOperation { + template <typename T> + static inline constexpr bool Do(T l, T r) { + return l > r; + } +}; + +struct GreaterThanOrEqualOperation { + template <typename T> + static inline constexpr bool Do(T l, T r) { + return l >= r; + } +}; + +// MaximumOperation is a bit more complex, in order to handle the variable +// number of parameters. +struct MaximumOperation { + template <typename T> + static inline constexpr T Do(T arg) { + // Base case for recursive template. + return arg; + } + + // Ideally, this would only use template<typename T>, but C++11 requires a + // full variadic template or C-style variadic function in order to accept a + // variable number of arguments. C-style variadic functions have no intrinsic + // way of figuring out how many arguments they receive, so we have to use a + // variadic template. + // + // The static_assert ensures that all arguments are actually the same type. + template <typename T1, typename T2, typename... T> + static inline constexpr T1 Do(T1 l, T2 r, T... rest) { + // C++11 std::max is not constexpr, so we can't just call it. + static_assert(::std::is_same<T1, T2>::value, + "Expected Do to be called with a proper intermediate type."); + return Do(l < r ? r : l, rest...); + } +}; + +//// Special operations, where either un-Known() operands do not always result +//// in un-Known() results, or where Known() operands do not always result in +//// Known() results. + +// Assertions for And and Or. +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr bool AssertBooleanOperationTypes() { + // And and Or are templates so that the Emboss code generator + // doesn't have to special case AND, but they should only be instantiated with + // <bool, bool, bool>. This pushes a bit of extra work onto the C++ compiler. + static_assert(::std::is_same<IntermediateT, bool>::value, + "EMBOSS BUG: Boolean operations must have bool IntermediateT."); + static_assert(::std::is_same<ResultT, bool>::value, + "EMBOSS BUG: Boolean operations must return bool."); + static_assert(::std::is_same<LeftT, bool>::value, + "EMBOSS BUG: Boolean operations require boolean operands."); + static_assert(::std::is_same<RightT, bool>::value, + "EMBOSS BUG: Boolean operations require boolean operands."); + return true; // A literal return type is required for a constexpr function. +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> And(Maybe<LeftT> l, Maybe<RightT> r) { + // If either value is false, the result is false, even if the other value is + // unknown. Otherwise, if either value is unknown, the result is unknown. + // Otherwise, both values are true, and the result is true. + return AssertBooleanOperationTypes<IntermediateT, ResultT, LeftT, RightT>(), + !l.ValueOr(true) || !r.ValueOr(true) + ? Maybe<ResultT>(false) + : (!l.Known() || !r.Known() ? Maybe<ResultT>() + : Maybe<ResultT>(true)); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> Or(Maybe<LeftT> l, Maybe<RightT> r) { + // If either value is true, the result is true, even if the other value is + // unknown. Otherwise, if either value is unknown, the result is unknown. + // Otherwise, both values are false, and the result is false. + return AssertBooleanOperationTypes<IntermediateT, ResultT, LeftT, RightT>(), + l.ValueOr(false) || r.ValueOr(false) + ? Maybe<ResultT>(true) + : (!l.Known() || !r.Known() ? Maybe<ResultT>() + : Maybe<ResultT>(false)); +} + +template <typename ResultT, typename ValueT> +inline constexpr Maybe<ResultT> MaybeStaticCast(Maybe<ValueT> value) { + return value.Known() + ? Maybe<ResultT>(static_cast<ResultT>(value.ValueOrDefault())) + : Maybe<ResultT>(); +} + +template <typename IntermediateT, typename ResultT, typename ConditionT, + typename TrueT, typename FalseT> +inline constexpr Maybe<ResultT> Choice(Maybe<ConditionT> condition, + Maybe<TrueT> if_true, + Maybe<FalseT> if_false) { + // Since the result of a condition could be any value from either if_true or + // if_false, it should be the same type as IntermediateT. + static_assert(::std::is_same<IntermediateT, ResultT>::value, + "Choice's IntermediateT should be the same as ResultT."); + static_assert(::std::is_same<ConditionT, bool>::value, + "Choice operation requires a boolean condition."); + // If the condition is un-Known(), then the result is un-Known(). Otherwise, + // the result is if_true if condition, or if_false if not condition. For + // integral types, ResultT may differ from TrueT or FalseT, so Known() results + // must be unwrapped, cast to ResultT, and re-wrapped in Maybe<ResultT>. For + // non-integral TrueT/FalseT/ResultT, the cast is unnecessary, but safe. + return condition.Known() ? condition.ValueOrDefault() + ? MaybeStaticCast<ResultT, TrueT>(if_true) + : MaybeStaticCast<ResultT, FalseT>(if_false) + : Maybe<ResultT>(); +} + +//// From here down: boilerplate instantiations of the various operations, which +//// only forward to MaybeDo: + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> Sum(Maybe<LeftT> l, Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, SumOperation, LeftT, RightT>(l, r); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> Difference(Maybe<LeftT> l, Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, DifferenceOperation, LeftT, RightT>(l, + r); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> Product(Maybe<LeftT> l, Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, ProductOperation, LeftT, RightT>(l, r); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> Equal(Maybe<LeftT> l, Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, EqualOperation, LeftT, RightT>(l, r); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> NotEqual(Maybe<LeftT> l, Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, NotEqualOperation, LeftT, RightT>(l, + r); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> LessThan(Maybe<LeftT> l, Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, LessThanOperation, LeftT, RightT>(l, + r); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> LessThanOrEqual(Maybe<LeftT> l, + Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, LessThanOrEqualOperation, LeftT, + RightT>(l, r); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> GreaterThan(Maybe<LeftT> l, Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, GreaterThanOperation, LeftT, RightT>( + l, r); +} + +template <typename IntermediateT, typename ResultT, typename LeftT, + typename RightT> +inline constexpr Maybe<ResultT> GreaterThanOrEqual(Maybe<LeftT> l, + Maybe<RightT> r) { + return MaybeDo<IntermediateT, ResultT, GreaterThanOrEqualOperation, LeftT, + RightT>(l, r); +} + +template <typename IntermediateT, typename ResultT, typename... ArgsT> +inline constexpr Maybe<ResultT> Maximum(Maybe<ArgsT>... args) { + return MaybeDo<IntermediateT, ResultT, MaximumOperation, ArgsT...>(args...); +} + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_ARITHMETIC_H_
diff --git a/public/emboss_arithmetic_test.cc b/public/emboss_arithmetic_test.cc new file mode 100644 index 0000000..3f41315 --- /dev/null +++ b/public/emboss_arithmetic_test.cc
@@ -0,0 +1,291 @@ +// 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. + +#include "public/emboss_arithmetic.h" + +#include <gtest/gtest.h> + +namespace emboss { +namespace support { + +// EXPECT_EQ uses operator==. For un-Known() Maybes, this follows the semantics +// for operator==(std::optional<T>, std::optional<T>), which returns true if +// neither argument has_value(). (It also matches Rust's Option and Haskell's +// Maybe.) +// +// Given the name "Known", it arguably should follow NaN != NaN semantics +// instead, but this is more useful for tests. +template <typename T> +constexpr inline bool operator==(const Maybe<T> &l, const Maybe<T> &r) { + return l.Known() == r.Known() && l.ValueOrDefault() == r.ValueOrDefault(); +} + +namespace test { + +using ::std::int32_t; +using ::std::int64_t; +using ::std::uint32_t; +using ::std::uint64_t; + +TEST(Sum, Sum) { + EXPECT_EQ(Maybe<int32_t>(0), (Sum<int32_t, int32_t, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<int32_t>(2147483647), + (Sum<int32_t, int32_t, int32_t, int32_t>(Maybe<int32_t>(2147483646), + Maybe<int32_t>(1)))); + EXPECT_EQ(Maybe<int32_t>(-2147483647 - 1), + (Sum<int32_t, int32_t, int32_t, int32_t>( + Maybe<int32_t>(-2147483647), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<uint32_t>(2147483648U), + (Sum<uint32_t, uint32_t, int32_t, int32_t>( + Maybe<int32_t>(2147483647), Maybe<int32_t>(1)))); + EXPECT_EQ(Maybe<int32_t>(2147483647), + (Sum<int64_t, int32_t, uint32_t, int32_t>( + Maybe<uint32_t>(2147483648U), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<int32_t>(), (Sum<int64_t, int32_t, uint32_t, int32_t>( + Maybe<uint32_t>(), Maybe<int32_t>(-1)))); +} + +TEST(Difference, Difference) { + EXPECT_EQ(Maybe<int32_t>(0), (Difference<int32_t, int32_t, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<int32_t>(2147483647), + (Difference<int32_t, int32_t, int32_t, int32_t>( + Maybe<int32_t>(2147483646), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<int32_t>(-2147483647 - 1), + (Difference<int32_t, int32_t, int32_t, int32_t>( + Maybe<int32_t>(-2147483647), Maybe<int32_t>(1)))); + EXPECT_EQ(Maybe<uint32_t>(2147483648U), + (Difference<uint32_t, uint32_t, int32_t, int32_t>( + Maybe<int32_t>(2147483647), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<int32_t>(2147483647), + (Difference<uint32_t, int32_t, uint32_t, int32_t>( + Maybe<uint32_t>(2147483648U), Maybe<int32_t>(1)))); + EXPECT_EQ(Maybe<int32_t>(-2147483647 - 1), + (Difference<int64_t, int32_t, int32_t, uint32_t>( + Maybe<int32_t>(1), Maybe<uint32_t>(2147483649U)))); + EXPECT_EQ(Maybe<int32_t>(), (Difference<int64_t, int32_t, int32_t, uint32_t>( + Maybe<int32_t>(1), Maybe<uint32_t>()))); +} + +TEST(Product, Product) { + EXPECT_EQ(Maybe<int32_t>(0), (Product<int32_t, int32_t, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<int32_t>(-2147483646), + (Product<int32_t, int32_t, int32_t, int32_t>( + Maybe<int32_t>(2147483646), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<int32_t>(-2147483647 - 1), + (Product<int32_t, int32_t, int32_t, int32_t>( + Maybe<int32_t>(-2147483647 - 1), Maybe<int32_t>(1)))); + EXPECT_EQ(Maybe<uint32_t>(2147483648U), + (Product<uint32_t, uint32_t, int32_t, int32_t>( + Maybe<int32_t>(1073741824), Maybe<int32_t>(2)))); + EXPECT_EQ(Maybe<uint32_t>(), (Product<uint32_t, uint32_t, int32_t, int32_t>( + Maybe<int32_t>(), Maybe<int32_t>(2)))); +} + +TEST(Equal, Equal) { + EXPECT_EQ(Maybe<bool>(true), (Equal<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<bool>(false), + (Equal<int32_t, bool, int32_t, int32_t>(Maybe<int32_t>(2147483646), + Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<bool>(true), + (Equal<int32_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647)))); + EXPECT_EQ(Maybe<bool>(false), + (Equal<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U)))); + EXPECT_EQ(Maybe<bool>(), + (Equal<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(), Maybe<uint32_t>(2147483648U)))); +} + +TEST(NotEqual, NotEqual) { + EXPECT_EQ(Maybe<bool>(false), (NotEqual<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<bool>(true), + (NotEqual<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(2147483646), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<bool>(false), + (NotEqual<int32_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647)))); + EXPECT_EQ(Maybe<bool>(true), + (NotEqual<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U)))); + EXPECT_EQ(Maybe<bool>(), + (NotEqual<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>()))); +} + +TEST(LessThan, LessThan) { + EXPECT_EQ(Maybe<bool>(false), (LessThan<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<bool>(false), + (LessThan<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(2147483646), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<bool>(false), + (LessThan<int32_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647)))); + EXPECT_EQ(Maybe<bool>(true), + (LessThan<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U)))); + EXPECT_EQ(Maybe<bool>(), + (LessThan<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(), Maybe<uint32_t>(2147483648U)))); +} + +TEST(LessThanOrEqual, LessThanOrEqual) { + EXPECT_EQ(Maybe<bool>(true), + (LessThanOrEqual<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<bool>(false), + (LessThanOrEqual<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(2147483646), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<bool>(true), + (LessThanOrEqual<int32_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647)))); + EXPECT_EQ(Maybe<bool>(true), + (LessThanOrEqual<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U)))); + EXPECT_EQ(Maybe<bool>(), + (LessThanOrEqual<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(), Maybe<uint32_t>(2147483648U)))); +} + +TEST(GreaterThan, GreaterThan) { + EXPECT_EQ(Maybe<bool>(false), (GreaterThan<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<bool>(true), + (GreaterThan<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(2147483646), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<bool>(false), + (GreaterThan<int32_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647)))); + EXPECT_EQ(Maybe<bool>(false), + (GreaterThan<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U)))); + EXPECT_EQ(Maybe<bool>(), + (GreaterThan<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(), Maybe<uint32_t>(2147483648U)))); +} + +TEST(GreaterThanOrEqual, GreaterThanOrEqual) { + EXPECT_EQ(Maybe<bool>(true), + (GreaterThanOrEqual<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(0), Maybe<int32_t>(0)))); + EXPECT_EQ(Maybe<bool>(true), + (GreaterThanOrEqual<int32_t, bool, int32_t, int32_t>( + Maybe<int32_t>(2147483646), Maybe<int32_t>(-1)))); + EXPECT_EQ(Maybe<bool>(true), + (GreaterThanOrEqual<int32_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647)))); + EXPECT_EQ(Maybe<bool>(false), + (GreaterThanOrEqual<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U)))); + EXPECT_EQ(Maybe<bool>(), + (GreaterThanOrEqual<int64_t, bool, int32_t, uint32_t>( + Maybe<int32_t>(), Maybe<uint32_t>(2147483648U)))); +} + +TEST(And, And) { + EXPECT_EQ(Maybe<bool>(true), (And<bool, bool, bool, bool>( + Maybe<bool>(true), Maybe<bool>(true)))); + EXPECT_EQ(Maybe<bool>(), + (And<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>(true)))); + EXPECT_EQ(Maybe<bool>(), + (And<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>()))); + EXPECT_EQ(Maybe<bool>(), + (And<bool, bool, bool, bool>(Maybe<bool>(true), Maybe<bool>()))); + EXPECT_EQ(Maybe<bool>(false), (And<bool, bool, bool, bool>( + Maybe<bool>(false), Maybe<bool>(true)))); + EXPECT_EQ(Maybe<bool>(false), + (And<bool, bool, bool, bool>(Maybe<bool>(false), Maybe<bool>()))); + EXPECT_EQ(Maybe<bool>(false), (And<bool, bool, bool, bool>( + Maybe<bool>(false), Maybe<bool>(false)))); + EXPECT_EQ(Maybe<bool>(false), (And<bool, bool, bool, bool>( + Maybe<bool>(true), Maybe<bool>(false)))); + EXPECT_EQ(Maybe<bool>(false), + (And<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>(false)))); +} + +TEST(Or, Or) { + EXPECT_EQ(Maybe<bool>(false), (Or<bool, bool, bool, bool>( + Maybe<bool>(false), Maybe<bool>(false)))); + EXPECT_EQ(Maybe<bool>(), + (Or<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>(false)))); + EXPECT_EQ(Maybe<bool>(), + (Or<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>()))); + EXPECT_EQ(Maybe<bool>(), + (Or<bool, bool, bool, bool>(Maybe<bool>(false), Maybe<bool>()))); + EXPECT_EQ(Maybe<bool>(true), (Or<bool, bool, bool, bool>(Maybe<bool>(false), + Maybe<bool>(true)))); + EXPECT_EQ(Maybe<bool>(true), + (Or<bool, bool, bool, bool>(Maybe<bool>(true), Maybe<bool>()))); + EXPECT_EQ(Maybe<bool>(true), + (Or<bool, bool, bool, bool>(Maybe<bool>(true), Maybe<bool>(true)))); + EXPECT_EQ(Maybe<bool>(true), (Or<bool, bool, bool, bool>( + Maybe<bool>(true), Maybe<bool>(false)))); + EXPECT_EQ(Maybe<bool>(true), + (Or<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>(true)))); +} + +TEST(Choice, Choice) { + EXPECT_EQ(Maybe<int>(), (Choice<int, int, bool, int, int>( + Maybe<bool>(), Maybe<int>(1), Maybe<int>(2)))); + EXPECT_EQ(Maybe<int>(1), + (Choice<int, int, bool, int, int>(Maybe<bool>(true), Maybe<int>(1), + Maybe<int>(2)))); + EXPECT_EQ(Maybe<int>(2), + (Choice<int, int, bool, int, int>(Maybe<bool>(false), Maybe<int>(1), + Maybe<int>(2)))); + EXPECT_EQ(Maybe<int>(), (Choice<int, int, bool, int, int>( + Maybe<bool>(true), Maybe<int>(), Maybe<int>(2)))); + EXPECT_EQ(Maybe<int>(), + (Choice<int, int, bool, int, int>(Maybe<bool>(false), Maybe<int>(1), + Maybe<int>()))); + EXPECT_EQ(Maybe<int64_t>(2), + (Choice<int64_t, int64_t, bool, int32_t, int32_t>( + Maybe<bool>(false), Maybe<int32_t>(1), Maybe<int32_t>(2)))); + EXPECT_EQ(Maybe<int64_t>(2), + (Choice<int64_t, int64_t, bool, int32_t, uint32_t>( + Maybe<bool>(false), Maybe<int32_t>(-1), Maybe<uint32_t>(2)))); + EXPECT_EQ(Maybe<int64_t>(-1), + (Choice<int64_t, int64_t, bool, int32_t, uint32_t>( + Maybe<bool>(true), Maybe<int32_t>(-1), Maybe<uint32_t>(2)))); + EXPECT_EQ(Maybe<bool>(true), + (Choice<bool, bool, bool, bool, bool>( + Maybe<bool>(false), Maybe<bool>(false), Maybe<bool>(true)))); +} + +TEST(Maximum, Maximum) { + EXPECT_EQ(Maybe<int>(100), (Maximum<int, int, int>(Maybe<int>(100)))); + EXPECT_EQ(Maybe<int>(99), + (Maximum<int, int, int, int>(Maybe<int>(99), Maybe<int>(50)))); + EXPECT_EQ(Maybe<int>(98), + (Maximum<int, int, int, int>(Maybe<int>(50), Maybe<int>(98)))); + EXPECT_EQ(Maybe<int>(97), + (Maximum<int, int, int, int, int>(Maybe<int>(50), Maybe<int>(70), + Maybe<int>(97)))); + EXPECT_EQ(Maybe<int>(), (Maximum<int, int, int, int, int>( + Maybe<int>(50), Maybe<int>(), Maybe<int>(97)))); + EXPECT_EQ(Maybe<int>(-100), + (Maximum<int, int, int, int, int>( + Maybe<int>(-120), Maybe<int>(-150), Maybe<int>(-100)))); + EXPECT_EQ(Maybe<int>(), (Maximum<int, int, int>(Maybe<int>()))); +} + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_array_view.h b/public/emboss_array_view.h new file mode 100644 index 0000000..12f0a59 --- /dev/null +++ b/public/emboss_array_view.h
@@ -0,0 +1,382 @@ +// 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. + +// View classes for arrays and bit arrays. +#ifndef EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_ +#define EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_ + +#include <cstddef> +#include <iterator> +#include <tuple> +#include <type_traits> + +#include "public/emboss_arithmetic.h" +#include "public/emboss_array_view.h" +#include "public/emboss_text_util.h" + +namespace emboss { + +// Forward declarations for use by WriteShorthandArrayCommentToTextStream. +namespace prelude { +template <class Parameters, class BitViewType> +class UIntView; +template <class Parameters, class BitViewType> +class IntView; +} // namespace prelude + +namespace support { + +// Advance direction for ElementViewIterator. +enum class ElementViewIteratorDirection { kForward, kReverse }; + +// Iterator adapter for elements in a GenericArrayView. +template <class GenericArrayView, ElementViewIteratorDirection kDirection> +class ElementViewIterator { + public: + using iterator_category = ::std::random_access_iterator_tag; + using value_type = typename GenericArrayView::ViewType; + using difference_type = ::std::ptrdiff_t; + using pointer = typename ::std::add_pointer<value_type>::type; + using reference = typename ::std::add_lvalue_reference<value_type>::type; + + explicit ElementViewIterator(const GenericArrayView *array_view, + ::std::ptrdiff_t index) + : array_view_(array_view), view_((*array_view)[index]), index_(index) {} + + ElementViewIterator() = default; + + reference operator*() { return view_; } + + pointer operator->() { return &view_; } + + ElementViewIterator &operator+=(difference_type d) { + index_ += (kDirection == ElementViewIteratorDirection::kForward ? d : -d); + view_ = (*array_view_)[index_]; + return *this; + } + + ElementViewIterator &operator-=(difference_type d) { return *this += (-d); } + + ElementViewIterator &operator++() { + *this += 1; + return *this; + } + + ElementViewIterator &operator--() { + *this -= 1; + return *this; + } + + ElementViewIterator operator++(int) { + auto copy = *this; + ++(*this); + return copy; + } + + ElementViewIterator operator--(int) { + auto copy = *this; + --(*this); + return copy; + } + + ElementViewIterator operator+(difference_type d) const { + auto copy = *this; + copy += d; + return copy; + } + + ElementViewIterator operator-(difference_type d) const { + return *this + (-d); + } + + difference_type operator-(const ElementViewIterator &other) const { + return kDirection == ElementViewIteratorDirection::kForward + ? index_ - other.index_ + : other.index_ - index_; + } + + bool operator==(const ElementViewIterator &other) const { + return array_view_ == other.array_view_ && index_ == other.index_; + } + + bool operator!=(const ElementViewIterator &other) const { + return !(*this == other); + } + + bool operator<(const ElementViewIterator &other) const { + return kDirection == ElementViewIteratorDirection::kForward + ? index_ < other.index_ + : other.index_ < index_; + } + + bool operator<=(const ElementViewIterator &other) const { + return kDirection == ElementViewIteratorDirection::kForward + ? index_ <= other.index_ + : other.index_ <= index_; + } + + bool operator>(const ElementViewIterator &other) const { + return !(*this <= other); + } + + bool operator>=(const ElementViewIterator &other) const { + return !(*this < other); + } + + private: + const GenericArrayView *array_view_; + typename GenericArrayView::ViewType view_; + ::std::ptrdiff_t index_; +}; + +// View for an array in a structure. +// +// ElementView should be the view class for a single array element (e.g., +// UIntView<...> or ArrayView<...>). +// +// BufferType is the storage type that will be passed into the array. +// +// kElementSize is the fixed size of a single element, in addressable units. +// +// kAddressableUnitSize is the size of a single addressable unit. It should be +// either 1 (one bit) or 8 (one byte). +// +// ElementViewParameterTypes is a list of the types of parameters which must be +// passed down to each element of the array. ElementViewParameterTypes can be +// empty. +template <class ElementView, class BufferType, ::std::size_t kElementSize, + ::std::size_t kAddressableUnitSize, + typename... ElementViewParameterTypes> +class GenericArrayView final { + public: + using ViewType = ElementView; + using ForwardIterator = + ElementViewIterator<GenericArrayView, + ElementViewIteratorDirection::kForward>; + using ReverseIterator = + ElementViewIterator<GenericArrayView, + ElementViewIteratorDirection::kReverse>; + + GenericArrayView() : buffer_() {} + explicit GenericArrayView(const ElementViewParameterTypes &... parameters, + BufferType buffer) + : parameters_{parameters...}, buffer_{buffer} {} + + ElementView operator[](::std::size_t index) const { + return IndexOperatorHelper<sizeof...(ElementViewParameterTypes) == + 0>::ConstructElement(parameters_, buffer_, + index); + } + + ForwardIterator begin() const { return ForwardIterator(this, 0); } + ForwardIterator end() const { return ForwardIterator(this, ElementCount()); } + ReverseIterator rbegin() const { + return ReverseIterator(this, ElementCount() - 1); + } + ReverseIterator rend() const { return ReverseIterator(this, -1); } + + // In order to selectively enable SizeInBytes and SizeInBits, it is + // necessary to make them into templates. Further, it is necessary for + // ::std::enable_if to have a dependency on the template parameter, otherwise + // SFINAE won't kick in. Thus, these are templated on an int, and that int + // is (spuriously) used as the left argument to `,` in the enable_if + // condition. The explicit cast to void is needed to silence GCC's + // -Wunused-value. + template <int N = 0> + typename ::std::enable_if<((void)N, kAddressableUnitSize == 8), + ::std::size_t>::type + SizeInBytes() const { + return buffer_.SizeInBytes(); + } + template <int N = 0> + typename ::std::enable_if<((void)N, kAddressableUnitSize == 1), + ::std::size_t>::type + SizeInBits() const { + return buffer_.SizeInBits(); + } + + ::std::size_t ElementCount() const { return SizeOfBuffer() / kElementSize; } + bool Ok() const { + if (!buffer_.Ok()) return false; + if (SizeOfBuffer() % kElementSize != 0) return false; + for (::std::size_t i = 0; i < ElementCount(); ++i) { + if (!(*this)[i].Ok()) return false; + } + return true; + } + template <class OtherElementView, class OtherBufferType> + bool Equals( + const GenericArrayView<OtherElementView, OtherBufferType, kElementSize, + kAddressableUnitSize> &other) const { + if (ElementCount() != other.ElementCount()) return false; + for (::std::size_t i = 0; i < ElementCount(); ++i) { + if (!(*this)[i].Equals(other[i])) return false; + } + return true; + } + template <class OtherElementView, class OtherBufferType> + bool UncheckedEquals( + const GenericArrayView<OtherElementView, OtherBufferType, kElementSize, + kAddressableUnitSize> &other) const { + if (ElementCount() != other.ElementCount()) return false; + for (::std::size_t i = 0; i < ElementCount(); ++i) { + if (!(*this)[i].UncheckedEquals(other[i])) return false; + } + return true; + } + bool IsComplete() const { return buffer_.Ok(); } + + template <class Stream> + bool UpdateFromTextStream(Stream *stream) const { + return ReadArrayFromTextStream(this, stream); + } + + template <class Stream> + void WriteToTextStream(Stream *stream, + const TextOutputOptions &options) const { + WriteArrayToTextStream(this, stream, options); + } + + BufferType BackingStorage() const { return buffer_; } + + private: + // This uses the same technique to select the correct definition of + // SizeOfBuffer() as in the SizeInBits()/SizeInBytes() selection above. + template <int N = 0> + typename ::std::enable_if<((void)N, kAddressableUnitSize == 8), + ::std::size_t>::type + SizeOfBuffer() const { + return SizeInBytes(); + } + template <int N = 0> + typename ::std::enable_if<((void)N, kAddressableUnitSize == 1), + ::std::size_t>::type + SizeOfBuffer() const { + return SizeInBits(); + } + + // This mess is needed to expand the parameters_ tuple into individual + // arguments to the ElementView constructor. If parameters_ has M elements, + // then: + // + // IndexOperatorHelper<false>::ConstructElement() calls + // IndexOperatorHelper<false, 0>::ConstructElement(), which calls + // IndexOperatorHelper<false, 0, 1>::ConstructElement(), and so on, up to + // IndexOperatorHelper<false, 0, 1, ..., M-1>::ConstructElement(), which calls + // IndexOperatorHelper<true, 0, 1, ..., M>::ConstructElement() + // + // That last call will resolve to the second, specialized version of + // IndexOperatorHelper. That version's ConstructElement() uses + // `std::get<N>(parameters)...`, which will be expanded into + // `std::get<0>(parameters), std::get<1>(parameters), std::get<2>(parameters), + // ..., std::get<M>(parameters)`. + // + // If there are 0 parameters, then operator[]() will call + // IndexOperatorHelper<true>::ConstructElement(), which still works -- + // `std::get<N>(parameters)...,` will be replaced by ``. + // + // In C++14, a lot of this can be replaced by std::index_sequence_of, and in + // C++17 it can be replaced with std::apply and a lambda. + // + // An alternate solution would be to force each parameterized view to have a + // constructor that accepts a tuple, instead of individual parameters, but + // that (further) complicates the matrix of constructors for view types. + template <bool, ::std::size_t... N> + struct IndexOperatorHelper { + static ElementView ConstructElement( + const ::std::tuple<ElementViewParameterTypes...> ¶meters, + BufferType buffer, ::std::size_t index) { + return IndexOperatorHelper< + sizeof...(ElementViewParameterTypes) == 1 + sizeof...(N), N..., + sizeof...(N)>::ConstructElement(parameters, buffer, index); + } + }; + + template </**/ ::std::size_t... N> + struct IndexOperatorHelper<true, N...> { + static ElementView ConstructElement( + const ::std::tuple<ElementViewParameterTypes...> ¶meters, + BufferType buffer, ::std::size_t index) { + return ElementView(::std::get<N>(parameters)..., + buffer.template GetOffsetStorage<kElementSize, 0>( + kElementSize * index, kElementSize)); + } + }; + + ::std::tuple<ElementViewParameterTypes...> parameters_; + BufferType buffer_; +}; + +// Optionally prints a shorthand representation of a BitArray in a comment. +template <class ElementView, class BufferType, size_t kElementSize, + size_t kAddressableUnitSize, class Stream> +void WriteShorthandArrayCommentToTextStream( + const GenericArrayView<ElementView, BufferType, kElementSize, + kAddressableUnitSize> *array, + Stream *stream, const TextOutputOptions &options) { + // Intentionally empty. Overload for specific element types. +} + +// Prints out the elements of an 8-bit Int or UInt array as characters. +template <class Array, class Stream> +void WriteShorthandAsciiArrayCommentToTextStream( + const Array *array, Stream *stream, const TextOutputOptions &options) { + if (!options.multiline()) return; + if (!options.comments()) return; + if (array->ElementCount() == 0) return; + static constexpr int kCharsPerBlock = 64; + static constexpr char kStandInForNonPrintableChar = '.'; + auto start_new_line = [&]() { + stream->Write("\n"); + stream->Write(options.current_indent()); + stream->Write("# "); + }; + for (int i = 0, n = array->ElementCount(); i < n; ++i) { + const int c = (*array)[i].Read(); + const bool c_is_printable = (c >= 32 && c <= 126); + const bool starting_new_block = ((i % kCharsPerBlock) == 0); + if (starting_new_block) start_new_line(); + stream->Write(c_is_printable ? static_cast<char>(c) + : kStandInForNonPrintableChar); + } +} + +// Overload for arrays of UInt. +// Prints out the elements as ASCII characters for arrays of UInt:8. +template <class BufferType, class BitViewType, class Stream, + size_t kElementSize, class Parameters, + class = typename ::std::enable_if<Parameters::kBits == 8>::type> +void WriteShorthandArrayCommentToTextStream( + const GenericArrayView<prelude::UIntView<Parameters, BitViewType>, + BufferType, kElementSize, 8> *array, + Stream *stream, const TextOutputOptions &options) { + WriteShorthandAsciiArrayCommentToTextStream(array, stream, options); +} + +// Overload for arrays of UInt. +// Prints out the elements as ASCII characters for arrays of Int:8. +template <class BufferType, class BitViewType, class Stream, + size_t kElementSize, class Parameters, + class = typename ::std::enable_if<Parameters::kBits == 8>::type> +void WriteShorthandArrayCommentToTextStream( + const GenericArrayView<prelude::IntView<Parameters, BitViewType>, + BufferType, kElementSize, 8> *array, + Stream *stream, const TextOutputOptions &options) { + WriteShorthandAsciiArrayCommentToTextStream(array, stream, options); +} + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_
diff --git a/public/emboss_array_view_test.cc b/public/emboss_array_view_test.cc new file mode 100644 index 0000000..1868d65 --- /dev/null +++ b/public/emboss_array_view_test.cc
@@ -0,0 +1,280 @@ +// 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. + +#include "public/emboss_array_view.h" + +#include <string> +#include <type_traits> + +#include "absl/strings/str_format.h" +#include <gtest/gtest.h> + +#include "public/emboss_prelude.h" + +namespace emboss { +namespace support { +namespace test { + +using ::emboss::prelude::IntView; +using ::emboss::prelude::UIntView; + +template <class ElementView, class BufferType, ::std::size_t kElementSize> +using ArrayView = GenericArrayView<ElementView, BufferType, kElementSize, 8>; + +template <class ElementView, class BufferType, ::std::size_t kElementSize> +using BitArrayView = GenericArrayView<ElementView, BufferType, kElementSize, 1>; + +template <size_t kBits> +using LittleEndianBitBlockN = + BitBlock<LittleEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>; + +template <size_t kBits> +using FixedUIntView = UIntView<FixedSizeViewParameters<kBits, AllValuesAreOk>, + LittleEndianBitBlockN<kBits>>; + +template <size_t kBits> +using FixedIntView = IntView<FixedSizeViewParameters<kBits, AllValuesAreOk>, + LittleEndianBitBlockN<kBits>>; + +TEST(ArrayView, Methods) { + uint8_t bytes[] = {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}; + auto byte_array = ArrayView<FixedUIntView<8>, ReadWriteContiguousBuffer, 1>{ + ReadWriteContiguousBuffer{bytes, sizeof bytes - 4}}; + EXPECT_EQ(sizeof bytes - 4, byte_array.SizeInBytes()); + EXPECT_EQ(bytes[0], byte_array[0].Read()); + EXPECT_EQ(bytes[1], byte_array[1].Read()); + EXPECT_EQ(bytes[2], byte_array[2].Read()); + EXPECT_DEATH(byte_array[sizeof bytes - 4].Read(), ""); + EXPECT_EQ(bytes[sizeof bytes - 4], + byte_array[sizeof bytes - 4].UncheckedRead()); + EXPECT_TRUE(byte_array[sizeof bytes - 5].IsComplete()); + EXPECT_FALSE(byte_array[sizeof bytes - 4].IsComplete()); + EXPECT_TRUE(byte_array.Ok()); + EXPECT_TRUE(byte_array.IsComplete()); + EXPECT_FALSE((ArrayView<FixedUIntView<8>, ReadWriteContiguousBuffer, 1>{ + ReadWriteContiguousBuffer{ + nullptr}}.Ok())); + EXPECT_TRUE(byte_array.IsComplete()); + + auto uint32_array = + ArrayView<FixedUIntView<32>, ReadWriteContiguousBuffer, 4>{ + ReadWriteContiguousBuffer{bytes, sizeof bytes - 4}}; + EXPECT_EQ(sizeof bytes - 4, uint32_array.SizeInBytes()); + EXPECT_TRUE(uint32_array[0].Ok()); + EXPECT_EQ(0x0d0e0f10, uint32_array[0].Read()); + EXPECT_EQ(0x090a0b0c, uint32_array[1].Read()); + EXPECT_EQ(0x05060708, uint32_array[2].Read()); + EXPECT_DEATH(uint32_array[3].Read(), ""); + EXPECT_EQ(0x01020304, uint32_array[3].UncheckedRead()); + EXPECT_TRUE(uint32_array[2].IsComplete()); + EXPECT_FALSE(uint32_array[3].IsComplete()); + EXPECT_TRUE(uint32_array.Ok()); + EXPECT_TRUE(uint32_array.IsComplete()); + EXPECT_FALSE((ArrayView<FixedUIntView<32>, ReadWriteContiguousBuffer, 1>{ + ReadWriteContiguousBuffer{ + nullptr}}.Ok())); +} + +TEST(ArrayView, Ok) { + uint8_t bytes[] = {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}; + // All elements are complete and, themselves, Ok(), so the array should be + // Ok(). + auto byte_array = ArrayView<FixedUIntView<16>, ReadWriteContiguousBuffer, 2>( + ReadWriteContiguousBuffer(bytes, sizeof bytes - 4)); + EXPECT_TRUE(byte_array.Ok()); + + // An array with a partial element at the end should not be Ok(). + byte_array = ArrayView<FixedUIntView<16>, ReadWriteContiguousBuffer, 2>( + ReadWriteContiguousBuffer(bytes, sizeof bytes - 3)); + EXPECT_FALSE(byte_array.Ok()); + + // An empty array should be Ok(). + byte_array = ArrayView<FixedUIntView<16>, ReadWriteContiguousBuffer, 2>( + ReadWriteContiguousBuffer(bytes, 0)); + EXPECT_TRUE(byte_array.Ok()); +} + +TEST(ArrayView, TextFormatInput) { + uint8_t bytes[16] = {0}; + auto byte_array = ArrayView<FixedUIntView<8>, ReadWriteContiguousBuffer, 1>{ + ReadWriteContiguousBuffer{bytes, sizeof bytes}}; + EXPECT_FALSE(UpdateFromText(byte_array, "")); + EXPECT_FALSE(UpdateFromText(byte_array, "[]")); + EXPECT_FALSE(UpdateFromText(byte_array, "{")); + EXPECT_FALSE(UpdateFromText(byte_array, "{[0")); + EXPECT_FALSE(UpdateFromText(byte_array, "{[0:0}")); + EXPECT_FALSE(UpdateFromText(byte_array, "{[]:0}")); + EXPECT_FALSE(UpdateFromText(byte_array, "{[0] 0}")); + EXPECT_FALSE(UpdateFromText(byte_array, "{[0] 0}")); + EXPECT_TRUE(UpdateFromText(byte_array, "{}")); + EXPECT_FALSE(UpdateFromText(byte_array, "{,1}")); + EXPECT_FALSE(UpdateFromText(byte_array, "{1,,}")); + EXPECT_FALSE(UpdateFromText(byte_array, "{ a }")); + EXPECT_TRUE(UpdateFromText(byte_array, "{1}")); + EXPECT_EQ(1, bytes[0]); + EXPECT_TRUE(UpdateFromText(byte_array, " {2}")); + EXPECT_EQ(2, bytes[0]); + EXPECT_TRUE(UpdateFromText(byte_array, " {\t\r\n4 } junk")); + EXPECT_EQ(4, bytes[0]); + EXPECT_TRUE(UpdateFromText(byte_array, "{3,}")); + EXPECT_EQ(3, bytes[0]); + EXPECT_FALSE(UpdateFromText(byte_array, "{4 5}")); + EXPECT_TRUE(UpdateFromText(byte_array, "{4, 5}")); + EXPECT_EQ(4, bytes[0]); + EXPECT_EQ(5, bytes[1]); + EXPECT_TRUE(UpdateFromText(byte_array, "{5, [6]: 5}")); + EXPECT_EQ(5, bytes[0]); + EXPECT_EQ(5, bytes[1]); + EXPECT_EQ(5, bytes[6]); + EXPECT_TRUE(UpdateFromText(byte_array, "{6, [7]:6, 6}")); + EXPECT_EQ(6, bytes[0]); + EXPECT_EQ(5, bytes[1]); + EXPECT_EQ(5, bytes[6]); + EXPECT_EQ(6, bytes[7]); + EXPECT_EQ(6, bytes[8]); + EXPECT_TRUE(UpdateFromText(byte_array, "{[7]: 7, 7, [0]: 7, 7}")); + EXPECT_EQ(7, bytes[0]); + EXPECT_EQ(7, bytes[1]); + EXPECT_EQ(7, bytes[7]); + EXPECT_EQ(7, bytes[8]); + EXPECT_FALSE(UpdateFromText(byte_array, "{[16]: 0}")); + EXPECT_FALSE(UpdateFromText(byte_array, "{[15]: 0, 0}")); +} + +TEST(ArrayView, TextFormatOutput_WithAndWithoutComments) { + signed char bytes[16] = {-3, 2, -1, 1, 0, 1, 1, 2, + 3, 5, 8, 13, 21, 34, 55, 89}; + auto buffer = ReadWriteContiguousBuffer{reinterpret_cast<uint8_t *>(bytes), + sizeof bytes}; + auto byte_array = + ArrayView<FixedIntView<8>, ReadWriteContiguousBuffer, 1>{buffer}; + EXPECT_EQ( + "{ [0]: -3, 2, -1, 1, 0, 1, 1, 2, [8]: 3, 5, 8, 13, 21, 34, 55, 89 }", + WriteToString(byte_array)); + EXPECT_EQ(WriteToString(byte_array, MultilineText()), + R"({ + # ............."7Y + [0]: -3 # -0x3 + [1]: 2 # 0x2 + [2]: -1 # -0x1 + [3]: 1 # 0x1 + [4]: 0 # 0x0 + [5]: 1 # 0x1 + [6]: 1 # 0x1 + [7]: 2 # 0x2 + [8]: 3 # 0x3 + [9]: 5 # 0x5 + [10]: 8 # 0x8 + [11]: 13 # 0xd + [12]: 21 # 0x15 + [13]: 34 # 0x22 + [14]: 55 # 0x37 + [15]: 89 # 0x59 +})"); + EXPECT_EQ( + WriteToString(byte_array, + MultilineText().WithIndent(" ").WithComments(false)), + R"({ + [0]: -3 + [1]: 2 + [2]: -1 + [3]: 1 + [4]: 0 + [5]: 1 + [6]: 1 + [7]: 2 + [8]: 3 + [9]: 5 + [10]: 8 + [11]: 13 + [12]: 21 + [13]: 34 + [14]: 55 + [15]: 89 +})"); + EXPECT_EQ( + WriteToString(byte_array, TextOutputOptions().WithNumericBase(16)), + "{ [0x0]: -0x3, 0x2, -0x1, 0x1, 0x0, 0x1, 0x1, 0x2, [0x8]: 0x3, 0x5, " + "0x8, 0xd, 0x15, 0x22, 0x37, 0x59 }"); +} + +TEST(ArrayView, TextFormatOutput_8BitIntElementTypes) { + uint8_t bytes[1] = {65}; + auto buffer = ReadWriteContiguousBuffer{bytes, sizeof bytes}; + const ::std::string expected_text = R"({ + # A + [0]: 65 # 0x41 +})"; + EXPECT_EQ( + WriteToString( + ArrayView<FixedIntView<8>, ReadWriteContiguousBuffer, 1>{buffer}, + MultilineText()), + expected_text); + EXPECT_EQ( + WriteToString( + ArrayView<FixedUIntView<8>, ReadWriteContiguousBuffer, 1>{buffer}, + MultilineText()), + expected_text); +} + +TEST(ArrayView, TextFormatOutput_16BitIntElementTypes) { + uint16_t bytes[1] = {65}; + auto buffer = ReadWriteContiguousBuffer{reinterpret_cast<uint8_t *>(bytes), + sizeof bytes}; + const ::std::string expected_text = R"({ + [0]: 65 # 0x41 +})"; + EXPECT_EQ( + WriteToString( + ArrayView<FixedIntView<16>, ReadWriteContiguousBuffer, 2>{buffer}, + MultilineText()), + expected_text); + EXPECT_EQ( + WriteToString( + ArrayView<FixedUIntView<16>, ReadWriteContiguousBuffer, 2>{buffer}, + MultilineText()), + expected_text); +} + +TEST(ArrayView, TextFormatOutput_MultilineComment) { + uint8_t bytes[65]; + for (::std::size_t i = 0; i < sizeof bytes; ++i) { + bytes[i] = '0' + (i % 10); + } + for (const ::std::size_t length : {63, 64, 65}) { + auto buffer = ReadWriteContiguousBuffer{bytes, length}; + ::std::string expected_text = + "{\n # " + "012345678901234567890123456789012345678901234567890123456789012"; + if (length > 63) expected_text += "3"; + if (length > 64) expected_text += "\n # 4"; + expected_text += "\n"; + for (::std::size_t i = 0; i < length; ++i) { + expected_text += + absl::StrFormat(" [%d]: %d # 0x%02x\n", i, bytes[i], bytes[i]); + } + expected_text += "}"; + EXPECT_EQ( + WriteToString( + ArrayView<FixedIntView<8>, ReadWriteContiguousBuffer, 1>{buffer}, + MultilineText()), + expected_text); + } +} + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_bit_util.h b/public/emboss_bit_util.h new file mode 100644 index 0000000..24ac132 --- /dev/null +++ b/public/emboss_bit_util.h
@@ -0,0 +1,79 @@ +// 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 file contains various utility routines for manipulating values at a low +// level, such as byte swaps and safe casts. +#ifndef EMBOSS_PUBLIC_EMBOSS_BIT_UTIL_H_ +#define EMBOSS_PUBLIC_EMBOSS_BIT_UTIL_H_ + +#include <cstdint> +#include <type_traits> + +#include "public/emboss_defines.h" + +namespace emboss { +namespace support { + +// Where possible, it is best to use byte swap builtins, but if they are not +// available ByteSwap can fall back to portable code. +inline constexpr ::std::uint8_t ByteSwap(::std::uint8_t x) { return x; } +inline constexpr ::std::uint16_t ByteSwap(::std::uint16_t x) { +#ifdef EMBOSS_BYTESWAP16 + return EMBOSS_BYTESWAP16(x); +#else + return (x << 8) | (x >> 8); +#endif +} +inline constexpr ::std::uint32_t ByteSwap(::std::uint32_t x) { +#ifdef EMBOSS_BYTESWAP32 + return EMBOSS_BYTESWAP32(x); +#else + return (static_cast</**/ ::std::uint32_t>( + ByteSwap(static_cast</**/ ::std::uint16_t>(x))) + << 16) | + ByteSwap(static_cast</**/ ::std::uint16_t>(x >> 16)); +#endif +} +inline constexpr ::std::uint64_t ByteSwap(::std::uint64_t x) { +#ifdef EMBOSS_BYTESWAP64 + return EMBOSS_BYTESWAP64(x); +#else + return (static_cast</**/ ::std::uint64_t>( + ByteSwap(static_cast</**/ ::std::uint32_t>(x))) + << 32) | + ByteSwap(static_cast</**/ ::std::uint32_t>(x >> 32)); +#endif +} + +// Masks the given value to the given number of bits. +template <typename T> +inline constexpr T MaskToNBits(T value, unsigned bits) { + static_assert(!::std::is_signed<T>::value, + "MaskToNBits only works on unsigned values."); + return bits < sizeof value * 8 ? value & ((static_cast<T>(1) << bits) - 1) + : value; +} + +template <typename T> +inline constexpr bool IsPowerOfTwo(T value) { + // This check relies on an old bit-counting trick; x & (x - 1) always has one + // fewer bit set to 1 than x (if x is nonzero), and powers of 2 always have + // exactly one 1 bit, thus x & (x - 1) == 0 if x is a power of 2. + return value > 0 && (value & (value - 1)) == 0; +} + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_BIT_UTIL_H_
diff --git a/public/emboss_bit_util_test.cc b/public/emboss_bit_util_test.cc new file mode 100644 index 0000000..8986383 --- /dev/null +++ b/public/emboss_bit_util_test.cc
@@ -0,0 +1,184 @@ +// 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. + +#include "public/emboss_bit_util.h" + +#include <gtest/gtest.h> + +namespace emboss { +namespace support { +namespace test { + +TEST(ByteSwap, ByteSwap) { + EXPECT_EQ(0x01, ByteSwap(uint8_t{0x01})); + EXPECT_EQ(0x0102, ByteSwap(uint16_t{0x0201})); + EXPECT_EQ(0x01020304, ByteSwap(uint32_t{0x04030201})); + EXPECT_EQ(0x0102030405060708UL, ByteSwap(uint64_t{0x0807060504030201UL})); +} + +TEST(MaskToNBits, MaskToNBits) { + EXPECT_EQ(0xff, MaskToNBits(0xffffffffU, 8)); + EXPECT_EQ(0x00, MaskToNBits(0xffffff00U, 8)); + EXPECT_EQ(0x01, MaskToNBits(0xffffffffU, 1)); + EXPECT_EQ(0x00, MaskToNBits(0xfffffffeU, 1)); + EXPECT_EQ(0xffffffffU, MaskToNBits(0xffffffffU, 32)); + EXPECT_EQ(0xffffffffffffffffU, MaskToNBits(0xffffffffffffffffU, 64)); + EXPECT_EQ(0xf, MaskToNBits(uint8_t{0xff}, 4)); +} + +TEST(IsPowerOfTwo, IsPowerOfTwo) { + EXPECT_TRUE(IsPowerOfTwo(1U)); + EXPECT_TRUE(IsPowerOfTwo(2U)); + EXPECT_TRUE(IsPowerOfTwo(1UL << 63)); + EXPECT_TRUE(IsPowerOfTwo(uint8_t{128})); + EXPECT_TRUE(IsPowerOfTwo(1)); + EXPECT_TRUE(IsPowerOfTwo(2)); + EXPECT_TRUE(IsPowerOfTwo(1L << 62)); + EXPECT_TRUE(IsPowerOfTwo(int8_t{64})); + + EXPECT_FALSE(IsPowerOfTwo(0U)); + EXPECT_FALSE(IsPowerOfTwo(3U)); + EXPECT_FALSE(IsPowerOfTwo((1UL << 63) - 1)); + EXPECT_FALSE(IsPowerOfTwo((1UL << 62) + 1)); + EXPECT_FALSE(IsPowerOfTwo((3UL << 62))); + EXPECT_FALSE(IsPowerOfTwo(::std::numeric_limits<uint64_t>::max())); + EXPECT_FALSE(IsPowerOfTwo(uint8_t{129})); + EXPECT_FALSE(IsPowerOfTwo(uint8_t{255})); + EXPECT_FALSE(IsPowerOfTwo(-1)); + EXPECT_FALSE(IsPowerOfTwo(-2)); + EXPECT_FALSE(IsPowerOfTwo(-3)); + EXPECT_FALSE(IsPowerOfTwo(::std::numeric_limits<int64_t>::min())); + EXPECT_FALSE(IsPowerOfTwo(::std::numeric_limits<int64_t>::max())); + EXPECT_FALSE(IsPowerOfTwo(0)); + EXPECT_FALSE(IsPowerOfTwo(3)); + EXPECT_FALSE(IsPowerOfTwo((1L << 62) - 1)); + EXPECT_FALSE(IsPowerOfTwo((1L << 61) + 1)); + EXPECT_FALSE(IsPowerOfTwo((3L << 61))); + EXPECT_FALSE(IsPowerOfTwo(int8_t{-1})); + EXPECT_FALSE(IsPowerOfTwo(int8_t{-128})); + EXPECT_FALSE(IsPowerOfTwo(int8_t{65})); + EXPECT_FALSE(IsPowerOfTwo(int8_t{127})); +} + +#if defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) +TEST(EndianConversion, LittleEndianToNative) { + ::std::uint16_t data16 = 0; + reinterpret_cast<char *>(&data16)[0] = 0x01; + reinterpret_cast<char *>(&data16)[1] = 0x02; + EXPECT_EQ(0x0201, EMBOSS_LITTLE_ENDIAN_TO_NATIVE(data16)); + + ::std::uint32_t data32 = 0; + reinterpret_cast<char *>(&data32)[0] = 0x01; + reinterpret_cast<char *>(&data32)[1] = 0x02; + reinterpret_cast<char *>(&data32)[2] = 0x03; + reinterpret_cast<char *>(&data32)[3] = 0x04; + EXPECT_EQ(0x04030201, EMBOSS_LITTLE_ENDIAN_TO_NATIVE(data32)); + + ::std::uint64_t data64 = 0; + reinterpret_cast<char *>(&data64)[0] = 0x01; + reinterpret_cast<char *>(&data64)[1] = 0x02; + reinterpret_cast<char *>(&data64)[2] = 0x03; + reinterpret_cast<char *>(&data64)[3] = 0x04; + reinterpret_cast<char *>(&data64)[4] = 0x05; + reinterpret_cast<char *>(&data64)[5] = 0x06; + reinterpret_cast<char *>(&data64)[6] = 0x07; + reinterpret_cast<char *>(&data64)[7] = 0x08; + EXPECT_EQ(0x0807060504030201, EMBOSS_LITTLE_ENDIAN_TO_NATIVE(data64)); +} +#endif // defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) + +#if defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) +TEST(EndianConversion, BigEndianToNative) { + ::std::uint16_t data16 = 0; + reinterpret_cast<char *>(&data16)[0] = 0x01; + reinterpret_cast<char *>(&data16)[1] = 0x02; + EXPECT_EQ(0x0102, EMBOSS_BIG_ENDIAN_TO_NATIVE(data16)); + + ::std::uint32_t data32 = 0; + reinterpret_cast<char *>(&data32)[0] = 0x01; + reinterpret_cast<char *>(&data32)[1] = 0x02; + reinterpret_cast<char *>(&data32)[2] = 0x03; + reinterpret_cast<char *>(&data32)[3] = 0x04; + EXPECT_EQ(0x01020304, EMBOSS_BIG_ENDIAN_TO_NATIVE(data32)); + + ::std::uint64_t data64 = 0; + reinterpret_cast<char *>(&data64)[0] = 0x01; + reinterpret_cast<char *>(&data64)[1] = 0x02; + reinterpret_cast<char *>(&data64)[2] = 0x03; + reinterpret_cast<char *>(&data64)[3] = 0x04; + reinterpret_cast<char *>(&data64)[4] = 0x05; + reinterpret_cast<char *>(&data64)[5] = 0x06; + reinterpret_cast<char *>(&data64)[6] = 0x07; + reinterpret_cast<char *>(&data64)[7] = 0x08; + EXPECT_EQ(0x0102030405060708, EMBOSS_BIG_ENDIAN_TO_NATIVE(data64)); +} +#endif // defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) + +#if defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) +TEST(EndianConversion, NativeToLittleEndian) { + ::std::uint16_t data16 = + EMBOSS_NATIVE_TO_LITTLE_ENDIAN(static_cast</**/ ::std::uint16_t>(0x0201)); + EXPECT_EQ(0x01, reinterpret_cast<char *>(&data16)[0]); + EXPECT_EQ(0x02, reinterpret_cast<char *>(&data16)[1]); + + ::std::uint32_t data32 = EMBOSS_NATIVE_TO_LITTLE_ENDIAN( + static_cast</**/ ::std::uint32_t>(0x04030201)); + EXPECT_EQ(0x01, reinterpret_cast<char *>(&data32)[0]); + EXPECT_EQ(0x02, reinterpret_cast<char *>(&data32)[1]); + EXPECT_EQ(0x03, reinterpret_cast<char *>(&data32)[2]); + EXPECT_EQ(0x04, reinterpret_cast<char *>(&data32)[3]); + + ::std::uint64_t data64 = EMBOSS_NATIVE_TO_LITTLE_ENDIAN( + static_cast</**/ ::std::uint64_t>(0x0807060504030201)); + EXPECT_EQ(0x01, reinterpret_cast<char *>(&data64)[0]); + EXPECT_EQ(0x02, reinterpret_cast<char *>(&data64)[1]); + EXPECT_EQ(0x03, reinterpret_cast<char *>(&data64)[2]); + EXPECT_EQ(0x04, reinterpret_cast<char *>(&data64)[3]); + EXPECT_EQ(0x05, reinterpret_cast<char *>(&data64)[4]); + EXPECT_EQ(0x06, reinterpret_cast<char *>(&data64)[5]); + EXPECT_EQ(0x07, reinterpret_cast<char *>(&data64)[6]); + EXPECT_EQ(0x08, reinterpret_cast<char *>(&data64)[7]); +} +#endif // defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) + +#if defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) +TEST(EndianConversion, NativeToBigEndian) { + ::std::uint16_t data16 = + EMBOSS_NATIVE_TO_BIG_ENDIAN(static_cast</**/ ::std::uint16_t>(0x0102)); + EXPECT_EQ(0x01, reinterpret_cast<char *>(&data16)[0]); + EXPECT_EQ(0x02, reinterpret_cast<char *>(&data16)[1]); + + ::std::uint32_t data32 = EMBOSS_NATIVE_TO_BIG_ENDIAN( + static_cast</**/ ::std::uint32_t>(0x01020304)); + EXPECT_EQ(0x01, reinterpret_cast<char *>(&data32)[0]); + EXPECT_EQ(0x02, reinterpret_cast<char *>(&data32)[1]); + EXPECT_EQ(0x03, reinterpret_cast<char *>(&data32)[2]); + EXPECT_EQ(0x04, reinterpret_cast<char *>(&data32)[3]); + + ::std::uint64_t data64 = EMBOSS_NATIVE_TO_BIG_ENDIAN( + static_cast</**/ ::std::uint64_t>(0x0102030405060708)); + EXPECT_EQ(0x01, reinterpret_cast<char *>(&data64)[0]); + EXPECT_EQ(0x02, reinterpret_cast<char *>(&data64)[1]); + EXPECT_EQ(0x03, reinterpret_cast<char *>(&data64)[2]); + EXPECT_EQ(0x04, reinterpret_cast<char *>(&data64)[3]); + EXPECT_EQ(0x05, reinterpret_cast<char *>(&data64)[4]); + EXPECT_EQ(0x06, reinterpret_cast<char *>(&data64)[5]); + EXPECT_EQ(0x07, reinterpret_cast<char *>(&data64)[6]); + EXPECT_EQ(0x08, reinterpret_cast<char *>(&data64)[7]); +} +#endif // defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_constant_view.h b/public/emboss_constant_view.h new file mode 100644 index 0000000..41785e5 --- /dev/null +++ b/public/emboss_constant_view.h
@@ -0,0 +1,51 @@ +// 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. + +#ifndef EMBOSS_PUBLIC_EMBOSS_CONSTANT_VIEW_H_ +#define EMBOSS_PUBLIC_EMBOSS_CONSTANT_VIEW_H_ + +#include "public/emboss_maybe.h" + +namespace emboss { +namespace support { + +// MaybeConstantView is a "view" type that "reads" a value passed into its +// constructor. +// +// This is used internally by generated structure view classes to provide views +// of parameters; in this way, parameters can be treated like fields in the +// generated code. +template <typename ValueT> +class MaybeConstantView { + public: + MaybeConstantView() : value_() {} + constexpr explicit MaybeConstantView(ValueT value) : value_(value) {} + MaybeConstantView(const MaybeConstantView &) = default; + MaybeConstantView(MaybeConstantView &&) = default; + MaybeConstantView &operator=(const MaybeConstantView &) = default; + MaybeConstantView &operator=(MaybeConstantView &&) = default; + ~MaybeConstantView() = default; + + constexpr ValueT Read() const { return value_.Value(); } + constexpr ValueT UncheckedRead() const { return value_.ValueOrDefault(); } + constexpr bool Ok() { return value_.Known(); } + + private: + ::emboss::support::Maybe<ValueT> value_; +}; + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_CONSTANT_VIEW_H_
diff --git a/public/emboss_constant_view_test.cc b/public/emboss_constant_view_test.cc new file mode 100644 index 0000000..aafc14e --- /dev/null +++ b/public/emboss_constant_view_test.cc
@@ -0,0 +1,62 @@ +// 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. + +#include "public/emboss_constant_view.h" + +#include <gtest/gtest.h> + +namespace emboss { +namespace support { +namespace test { + +TEST(MaybeConstantViewTest, Read) { + EXPECT_EQ(7, MaybeConstantView</**/ ::std::uint8_t>(7).Read()); + EXPECT_DEATH(MaybeConstantView</**/ ::std::uint8_t>().Read(), "Known\\(\\)"); +} + +TEST(MaybeConstantViewTest, UncheckedRead) { + EXPECT_EQ(7, MaybeConstantView</**/ ::std::uint8_t>(7).UncheckedRead()); + EXPECT_EQ(0, MaybeConstantView</**/ ::std::uint8_t>().UncheckedRead()); +} + +TEST(MaybeConstantViewTest, Ok) { + EXPECT_TRUE(MaybeConstantView</**/ ::std::uint8_t>(7).Ok()); + EXPECT_FALSE(MaybeConstantView</**/ ::std::uint8_t>().Ok()); +} + +TEST(MaybeConstantViewTest, CopyConstruction) { + auto with_value = MaybeConstantView</**/ ::std::uint8_t>(7); + auto copied_with_value = with_value; + EXPECT_EQ(7, copied_with_value.Read()); + + auto without_value = MaybeConstantView</**/ ::std::uint8_t>(); + auto copied_without_value = without_value; + EXPECT_FALSE(copied_without_value.Ok()); +} + +TEST(MaybeConstantViewTest, Assignment) { + auto with_value = MaybeConstantView</**/ ::std::uint8_t>(7); + MaybeConstantView</**/ ::std::uint8_t> copied_with_value; + copied_with_value = with_value; + EXPECT_EQ(7, copied_with_value.Read()); + + auto without_value = MaybeConstantView</**/ ::std::uint8_t>(); + MaybeConstantView</**/ ::std::uint8_t> copied_without_value; + copied_without_value = without_value; + EXPECT_FALSE(copied_without_value.Ok()); +} + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_cpp_types.h b/public/emboss_cpp_types.h new file mode 100644 index 0000000..a4aa3b2 --- /dev/null +++ b/public/emboss_cpp_types.h
@@ -0,0 +1,125 @@ +// 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 file contains various C++ type aliases for use in Emboss. +#ifndef EMBOSS_PUBLIC_EMBOSS_CPP_TYPES_H_ +#define EMBOSS_PUBLIC_EMBOSS_CPP_TYPES_H_ + +#include <climits> +#include <cstdint> +#include <type_traits> + +namespace emboss { +namespace support { + +static_assert(sizeof(long long) * CHAR_BIT >= 64, // NOLINT + "Emboss requires that long long is at least 64 bits."); + +// FloatType<n_bits>::Type is the C++ floating-point type of the appropriate +// size. +template <int kBits> +struct FloatType final { + static_assert(kBits == 32 || kBits == 64, "Unknown floating-point size."); +}; +template <> +struct FloatType<64> final { + static_assert(sizeof(double) * CHAR_BIT == 64, + "C++ double type must be 64 bits!"); + using Type = double; + using UIntType = ::std::uint64_t; +}; +template <> +struct FloatType<32> final { + static_assert(sizeof(float) * CHAR_BIT == 32, + "C++ float type must be 32 bits!"); + using Type = float; + using UIntType = ::std::uint32_t; +}; + +// LeastWidthInteger<n_bits>::Unsigned is the smallest uintNN_t type that can +// hold n_bits or more. LeastWidthInteger<n_bits>::Signed is the corresponding +// signed type. +template <int kBits> +struct LeastWidthInteger final { + static_assert(kBits <= 64, "Only bit sizes up to 64 are supported."); + using Unsigned = typename LeastWidthInteger<kBits + 1>::Unsigned; + using Signed = typename LeastWidthInteger<kBits + 1>::Signed; +}; +template <> +struct LeastWidthInteger<64> final { + using Unsigned = ::std::uint64_t; + using Signed = ::std::int64_t; +}; +template <> +struct LeastWidthInteger<32> final { + using Unsigned = ::std::uint32_t; + using Signed = ::std::int32_t; +}; +template <> +struct LeastWidthInteger<16> final { + using Unsigned = ::std::uint16_t; + using Signed = ::std::int16_t; +}; +template <> +struct LeastWidthInteger<8> final { + using Unsigned = ::std::uint8_t; + using Signed = ::std::int8_t; +}; + +// IsChar<T>::value is true if T is a character type; i.e. const? volatile? +// (signed|unsigned)? char. +template <typename T> +struct IsChar { + // Note that 'char' is a distinct type from 'signed char' and 'unsigned char'. + static constexpr bool value = + ::std::is_same<char, typename ::std::remove_cv<T>::type>::value || + ::std::is_same<unsigned char, + typename ::std::remove_cv<T>::type>::value || + ::std::is_same<signed char, typename ::std::remove_cv<T>::type>::value; +}; + +// The static member variable requires a definition. +template <typename T> +constexpr bool IsChar<T>::value; + +// AddSourceConst<SourceT, DestT>::Type is DestT's base type with const added if +// SourceT is const. +template <typename SourceT, typename DestT> +struct AddSourceConst { + using Type = typename ::std::conditional< + /**/ ::std::is_const<SourceT>::value, + typename ::std::add_const<DestT>::type, DestT>::type; +}; + +// AddSourceVolatile<SourceT, DestT>::Type is DestT's base type with volatile +// added if SourceT is volatile. +template <typename SourceT, typename DestT> +struct AddSourceVolatile { + using Type = typename ::std::conditional< + /**/ ::std::is_volatile<SourceT>::value, + typename ::std::add_volatile<DestT>::type, DestT>::type; +}; + +// AddCV<SourceT, DestT>::Type is DestT's base type with SourceT's const and +// volatile qualifiers added, if any. +template <typename SourceT, typename DestT> +struct AddSourceCV { + using Type = typename AddSourceConst< + SourceT, typename AddSourceVolatile<SourceT, DestT>::Type>::Type; +}; + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_CPP_TYPES_H_
diff --git a/public/emboss_cpp_types_test.cc b/public/emboss_cpp_types_test.cc new file mode 100644 index 0000000..e56858a --- /dev/null +++ b/public/emboss_cpp_types_test.cc
@@ -0,0 +1,199 @@ +// 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. + +#include "public/emboss_cpp_types.h" + +#include <gtest/gtest.h> + +namespace emboss { +namespace support { +namespace test { + +TEST(FloatTypes, Types) { + EXPECT_EQ(32 / CHAR_BIT, sizeof(FloatType<32>::Type)); + EXPECT_EQ(64 / CHAR_BIT, sizeof(FloatType<64>::Type)); + EXPECT_EQ(32 / CHAR_BIT, sizeof(FloatType<32>::UIntType)); + EXPECT_EQ(64 / CHAR_BIT, sizeof(FloatType<64>::UIntType)); + EXPECT_TRUE(::std::is_floating_point<FloatType<32>::Type>::value); + EXPECT_TRUE(::std::is_floating_point<FloatType<64>::Type>::value); + EXPECT_TRUE( + (::std::is_same<FloatType<32>::UIntType, ::std::uint32_t>::value)); + EXPECT_TRUE( + (::std::is_same<FloatType<64>::UIntType, ::std::uint64_t>::value)); +} + +TEST(LeastWidthInteger, Types) { + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<1>::Unsigned, ::std::uint8_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<1>::Signed, ::std::int8_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<4>::Unsigned, ::std::uint8_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<4>::Signed, ::std::int8_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<7>::Unsigned, ::std::uint8_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<7>::Signed, ::std::int8_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<8>::Unsigned, ::std::uint8_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<8>::Signed, ::std::int8_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<9>::Unsigned, ::std::uint16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<9>::Signed, ::std::int16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<12>::Unsigned, ::std::uint16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<12>::Signed, ::std::int16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<15>::Unsigned, ::std::uint16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<15>::Signed, ::std::int16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<16>::Unsigned, ::std::uint16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<16>::Signed, ::std::int16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<17>::Unsigned, ::std::uint32_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<17>::Signed, ::std::int32_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<28>::Unsigned, ::std::uint32_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<28>::Signed, ::std::int32_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<31>::Unsigned, ::std::uint32_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<31>::Signed, ::std::int32_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<32>::Unsigned, ::std::uint32_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<32>::Signed, ::std::int32_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<33>::Unsigned, ::std::uint64_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<33>::Signed, ::std::int64_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<60>::Unsigned, ::std::uint64_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<60>::Signed, ::std::int64_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<63>::Unsigned, ::std::uint64_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<63>::Signed, ::std::int64_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<64>::Unsigned, ::std::uint64_t>::value)); + EXPECT_TRUE(( + ::std::is_same<LeastWidthInteger<64>::Signed, ::std::int64_t>::value)); +} + +TEST(IsChar, CharTypes) { + EXPECT_TRUE(IsChar<char>::value); + EXPECT_TRUE(IsChar<unsigned char>::value); + EXPECT_TRUE(IsChar<signed char>::value); + EXPECT_TRUE(IsChar<const char>::value); + EXPECT_TRUE(IsChar<const unsigned char>::value); + EXPECT_TRUE(IsChar<const signed char>::value); + EXPECT_TRUE(IsChar<volatile char>::value); + EXPECT_TRUE(IsChar<volatile unsigned char>::value); + EXPECT_TRUE(IsChar<volatile signed char>::value); + EXPECT_TRUE(IsChar<const volatile char>::value); + EXPECT_TRUE(IsChar<const volatile unsigned char>::value); + EXPECT_TRUE(IsChar<const volatile signed char>::value); +} + +TEST(IsChar, NonCharTypes) { + struct OneByte { char c; }; + EXPECT_EQ(1, sizeof(OneByte)); + EXPECT_FALSE(IsChar<int>::value); + EXPECT_FALSE(IsChar<unsigned>::value); + EXPECT_FALSE(IsChar<const int>::value); + EXPECT_FALSE(IsChar<OneByte>::value); +} + +TEST(AddSourceConst, AddSourceConst) { + EXPECT_TRUE( + (::std::is_same<const char, + typename AddSourceConst<const int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same< + const volatile char, + typename AddSourceConst<const int, volatile char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<char, typename AddSourceConst<int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same< + char, typename AddSourceConst<volatile int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<const char, + typename AddSourceConst<int, const char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<const char, typename AddSourceConst< + const int, const char>::Type>::value)); +} + +TEST(AddSourceVolatile, AddSourceVolatile) { + EXPECT_TRUE( + (::std::is_same<volatile char, typename AddSourceVolatile< + volatile int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same< + const volatile char, + typename AddSourceVolatile<volatile int, const char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<char, + typename AddSourceVolatile<int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same< + char, typename AddSourceVolatile<const int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<volatile char, typename AddSourceVolatile< + int, volatile char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<volatile char, + typename AddSourceVolatile<volatile int, + volatile char>::Type>::value)); +} + +TEST(AddSourceCV, AddSourceCV) { + EXPECT_TRUE( + (::std::is_same<const char, + typename AddSourceCV<const int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<volatile char, + typename AddSourceCV<volatile int, char>::Type>::value)); + EXPECT_TRUE((::std::is_same< + const volatile char, + typename AddSourceCV<volatile int, const char>::Type>::value)); + EXPECT_TRUE((::std::is_same< + const volatile char, + typename AddSourceCV<const int, volatile char>::Type>::value)); + EXPECT_TRUE((::std::is_same< + const volatile char, + typename AddSourceCV<const volatile int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<char, typename AddSourceCV<int, char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same<volatile char, + typename AddSourceCV<int, volatile char>::Type>::value)); + EXPECT_TRUE( + (::std::is_same< + volatile char, + typename AddSourceCV<volatile int, volatile char>::Type>::value)); +} +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_cpp_util.h b/public/emboss_cpp_util.h new file mode 100644 index 0000000..624f931 --- /dev/null +++ b/public/emboss_cpp_util.h
@@ -0,0 +1,31 @@ +// 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 exports utilities that are needed for all Emboss-generated C++ +// code. +#ifndef EMBOSS_PUBLIC_EMBOSS_CPP_UTIL_H_ +#define EMBOSS_PUBLIC_EMBOSS_CPP_UTIL_H_ + +#include "public/emboss_arithmetic.h" +#include "public/emboss_array_view.h" +#include "public/emboss_bit_util.h" +#include "public/emboss_constant_view.h" +#include "public/emboss_cpp_types.h" +#include "public/emboss_defines.h" +#include "public/emboss_enum_view.h" +#include "public/emboss_memory_util.h" +#include "public/emboss_text_util.h" +#include "public/emboss_view_parameters.h" + +#endif // EMBOSS_PUBLIC_EMBOSS_CPP_UTIL_H_
diff --git a/public/emboss_cpp_util_google_integration_test.cc b/public/emboss_cpp_util_google_integration_test.cc new file mode 100644 index 0000000..c634b10 --- /dev/null +++ b/public/emboss_cpp_util_google_integration_test.cc
@@ -0,0 +1,37 @@ +// 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. + +#include "public/emboss_cpp_util.h" + +#include <gtest/gtest.h> +#include "third_party/absl/strings/string_view.h" + +namespace emboss { +namespace support { +namespace test { + +TEST(TextStream, Construction) { + absl::string_view view_text = "gh"; + auto text_stream = TextStream(view_text); + char result; + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('g', result); + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('h', result); + EXPECT_FALSE(text_stream.Read(&result)); +} + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_cpp_util_nc.cc b/public/emboss_cpp_util_nc.cc new file mode 100644 index 0000000..7e72db7 --- /dev/null +++ b/public/emboss_cpp_util_nc.cc
@@ -0,0 +1,43 @@ +// 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. + +#include "public/emboss_cpp_util.h" + +#include <string> + +namespace emboss { +namespace { + +void X() { +#ifdef TEST_CANNOT_CONSTRUCT_READ_WRITE_CONTIGUOUS_BUFFER_FROM_STRING + ::std::string foo = "string"; + + // Read-only ContiguousBuffer should be fine. + (void)ContiguousBuffer<const char>(foo); + + // Read-write ContiguousBuffer should be fail. + (void)ContiguousBuffer<char>(foo); +#endif // TEST_CANNOT_CONSTRUCT_READ_WRITE_CONTIGUOUS_BUFFER_FROM_STRING + +#ifdef TEST_CANNOT_CONSTRUCT_NON_BYTE_CONTIGUOUS_BUFFER + // ContiguousBuffer<char>(nullptr) should be fine... + (void)ContiguousBuffer<char>(nullptr); + + // ... but ContiguousBuffer<int>(nullptr) should not. + (void)ContiguousBuffer<int>(nullptr); +#endif // TEST_CANNOT_CONSTRUCT_NON_BYTE_CONTIGUOUS_BUFFER +} + +} // namespace +} // namespace emboss
diff --git a/public/emboss_defines.h b/public/emboss_defines.h new file mode 100644 index 0000000..3be4d66 --- /dev/null +++ b/public/emboss_defines.h
@@ -0,0 +1,167 @@ +// 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 #defines that are used to control Emboss's generated +// code. +#ifndef EMBOSS_PUBLIC_EMBOSS_DEFINES_H_ +#define EMBOSS_PUBLIC_EMBOSS_DEFINES_H_ + +// TODO(bolms): Add an explicit extension point for these macros. +#include <assert.h> +#define EMBOSS_CHECK(x) assert((x)) +#define EMBOSS_CHECK_LE(x, y) assert((x) <= (y)) +#define EMBOSS_CHECK_GE(x, y) assert((x) >= (y)) +#define EMBOSS_CHECK_GT(x, y) assert((x) > (y)) +#define EMBOSS_CHECK_EQ(x, y) assert((x) == (y)) + +// If EMBOSS_FORCE_ALL_CHECKS is #defined, then all checks are enabled even in +// optimized modes. Otherwise, EMBOSS_DCHECK only runs in debug mode. +#ifdef EMBOSS_FORCE_ALL_CHECKS +#define EMBOSS_DCHECK_EQ(x, y) assert((x) == (y)) +#define EMBOSS_DCHECK_GE(x, y) assert((x) >= (y)) +#else +#define EMBOSS_DCHECK_EQ(x, y) assert((x) == (y)) +#define EMBOSS_DCHECK_GE(x, y) assert((x) >= (y)) +#endif // EMBOSS_FORCE_ALL_CHECKS + +// Technically, the mapping from pointers to integers is implementation-defined, +// but the standard states "[ Note: It is intended to be unsurprising to those +// who know the addressing structure of the underlying machine. - end note ]," +// so this should be a reasonably safe way to check that a pointer is aligned. +#define EMBOSS_DCHECK_POINTER_ALIGNMENT(p, align, offset) \ + EMBOSS_DCHECK_EQ(reinterpret_cast</**/ ::std::uintptr_t>((p)) % (align), \ + (offset)) +#define EMBOSS_CHECK_POINTER_ALIGNMENT(p, align, offset) \ + EMBOSS_CHECK_EQ(reinterpret_cast</**/ ::std::uintptr_t>((p)) % (align), \ + (offset)) + +// !! WARNING !! +// +// It is possible to pre-#define a number of macros used below to influence +// Emboss's system-specific optimizations. If so, they *must* be #defined the +// same way in every compilation unit that #includes any Emboss-related header +// or generated code, before any such headers are #included, or else there is a +// real risk of ODR violations. It is recommended that any EMBOSS_* #defines +// are added to the global C++ compiler options in your build system, rather +// than being individually specified in source files. +// +// TODO(bolms): Add an #include for a site-specific header file, where +// site-specific customizations can be placed, and recommend that any overrides +// be placed in that header. Further, use that header for the EMBOSS_CHECK_* +// #defines, above. + +// EMBOSS_NO_OPTIMIZATIONS is used to turn off all system-specific +// optimizations. This is mostly intended for testing, but could be used if +// optimizations are causing problems. +#if !defined(EMBOSS_NO_OPTIMIZATIONS) +#if defined(__GNUC__) // GCC and "compatible" compilers, such as Clang. + +// GCC, Clang, and ICC only support two's-complement systems, so it is safe to +// assume two's-complement for those systems. In particular, this means that +// static_cast<int>() will treat its argument as a two's-complement bit pattern, +// which means that it is reasonable to static_cast<int>(some_unsigned_value). +// +// TODO(bolms): Are there actually any non-archaic systems that use any integer +// types other than 2's-complement? +#ifndef EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT +#define EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT 1 +#endif + +#if !defined(__INTEL_COMPILER) +// On systems with known host byte order, Emboss can always use memcpy to safely +// and relatively efficiently read and write values from and to memory. +// However, memcpy cannot assume that its pointers are aligned. On common +// platforms, particularly x86, this almost never matters; however, on some +// systems this can add considerable overhead, as memcpy must either perform +// byte-by-byte copies or perform tests to determine pointer alignment and then +// dispatch to alignment-specific code. +// +// Platforms with no alignment restrictions: +// +// * x86 (except for a few SSE instructions like movdqa: see +// http://pzemtsov.github.io/2016/11/06/bug-story-alignment-on-x86.html) +// * ARM systems with ARMv6 and later ISAs +// * High-end POWER-based systems +// * POWER-based systems with an alignment exception handler installed (but note +// that emulated unaligned reads are *very* slow) +// +// Platforms with alignment restrictions: +// +// * MicroBlaze +// * Emscripten +// * Low-end bare-metal POWER-based systems +// * ARM systems with ARMv5 and earlier ISAs +// * x86 with the AC bit of EEFLAGS enabled (but note that this is never enabled +// on any normal system, and, e.g., you will get crashes in glibc if you try +// to enable it) +// +// The naive solution is to reinterpret_cast to a type like uint32_t, then read +// or write through that pointer; however, this can easily run afoul of C++'s +// type aliasing rules and result in undefined behavior. +// +// On GCC, there is a solution to this: use the "__may_alias__" type attribute, +// which essentially forces the type to have the same aliasing rules as char; +// i.e., it is safe to read and write through a pointer derived from +// reinterpret_cast<T __attribute__((__may_alias__)) *>, just as it is safe to +// read and write through a pointer derived from reinterpret_cast<char *>. +// +// Note that even though ICC pretends to be compatible with GCC by defining +// __GNUC__, it does *not* appear to support the __may_alias__ attribute. +// (TODO(bolms): verify this if/when Emboss explicitly supports ICC.) +// +// Note the lack of parentheses around 't' in the expansion: unfortunately, +// GCC's attribute syntax disallows parentheses in that particular position. +#define EMBOSS_ALIAS_SAFE_POINTER_CAST(t, x) \ + reinterpret_cast<t __attribute__((__may_alias__)) *>((x)) +#endif // !defined(__INTEL_COMPILER) + +// GCC supports __BYTE_ORDER__ of __ORDER_LITTLE_ENDIAN__, __ORDER_BIG_ENDIAN__, +// and __ORDER_PDP_ENDIAN__. Since all available test systems are +// __ORDER_LITTLE_ENDIAN__, only little-endian hosts get optimized code paths; +// however, big-endian support ought to be trivial to add. +// +// There are no plans to support PDP-endian systems. +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +// EMBOSS_LITTLE_ENDIAN_TO_NATIVE and EMBOSS_BIG_ENDIAN_TO_NATIVE can be used to +// fix up integers after a little- or big-endian value has been memcpy'ed into +// them. +// +// On little-endian systems, no fixup is needed for little-endian sources, but +// big-endian sources require a byte swap. +#define EMBOSS_LITTLE_ENDIAN_TO_NATIVE(x) (x) +#define EMBOSS_NATIVE_TO_LITTLE_ENDIAN(x) (x) +#define EMBOSS_BIG_ENDIAN_TO_NATIVE(x) (::emboss::support::ByteSwap((x))) +#define EMBOSS_NATIVE_TO_BIG_ENDIAN(x) (::emboss::support::ByteSwap((x))) +// TODO(bolms): Find a way to test on a big-endian architecture, and add support +// for __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#endif // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + +// Prior to version 4.8, __builtin_bswap16 was not available on all platforms. +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52624 +// +// Clang pretends to be an earlier GCC, but does support __builtin_bswap16. +// Clang recommends using __has_builtin(__builtin_bswap16), but unfortunately +// that fails to compile on GCC, even with defined(__has_builtin) && +// __has_builtin(__builtin_bswap16), so instead Emboss just checks for +// defined(__clang__). +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || defined(__clang__) +#define EMBOSS_BYTESWAP16(x) __builtin_bswap16((x)) +#endif // __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) +#define EMBOSS_BYTESWAP32(x) __builtin_bswap32((x)) +#define EMBOSS_BYTESWAP64(x) __builtin_bswap64((x)) + +#endif // defined(__GNUC__) +#endif // !defined(EMBOSS_NO_OPTIMIZATIONS) + +#endif // EMBOSS_PUBLIC_EMBOSS_DEFINES_H_
diff --git a/public/emboss_defines_test.cc b/public/emboss_defines_test.cc new file mode 100644 index 0000000..198b0c4 --- /dev/null +++ b/public/emboss_defines_test.cc
@@ -0,0 +1,59 @@ +// 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. + +#include "public/emboss_defines.h" + +#include <cstdint> + +#include <gtest/gtest.h> + +namespace emboss { +namespace support { +namespace test { + +TEST(CheckPointerAlignment, Aligned) { + ::std::uint32_t t; + EMBOSS_DCHECK_POINTER_ALIGNMENT(&t, sizeof t, 0); + EMBOSS_DCHECK_POINTER_ALIGNMENT(&t, 1, 0); + EMBOSS_DCHECK_POINTER_ALIGNMENT(reinterpret_cast<char *>(&t) + 1, sizeof t, + 1); + EMBOSS_DCHECK_POINTER_ALIGNMENT(reinterpret_cast<char *>(&t) + 1, 1, 0); +} + +TEST(CheckPointerAlignment, Misaligned) { + ::std::uint32_t t; + EXPECT_DEATH(EMBOSS_DCHECK_POINTER_ALIGNMENT(&t, sizeof t, 1), ""); + EXPECT_DEATH(EMBOSS_DCHECK_POINTER_ALIGNMENT(reinterpret_cast<char *>(&t) + 1, + sizeof t, 0), + ""); +} + +#if EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT +TEST(SystemIsTwosComplement, CastToSigned) { + EXPECT_EQ(-static_cast</**/ ::std::int64_t>(0x80000000), + static_cast</**/ ::std::int32_t>(0x80000000)); +} +#endif // EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT + +// Note: I (bolms@) can't think of a way to truly test +// EMBOSS_ALIAS_SAFE_POINTER_CAST, since the compiler might let it work even if +// it's not "supposed" to. (E.g., even with -fstrict-aliasing, GCC doesn't +// always take advantage of strict aliasing to do any optimizations.) + +// The native <=> fixed endian macros are tested in emboss_bit_util_test.cc, +// since their expansions rely on emboss_bit_util.h. + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_enum_view.h b/public/emboss_enum_view.h new file mode 100644 index 0000000..6ed9453 --- /dev/null +++ b/public/emboss_enum_view.h
@@ -0,0 +1,159 @@ +// 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. + +// View class template for enums. +#ifndef EMBOSS_PUBLIC_EMBOSS_ENUM_VIEW_H_ +#define EMBOSS_PUBLIC_EMBOSS_ENUM_VIEW_H_ + +#include <cctype> +#include <cstdint> +#include <string> +#include <utility> + +#include "public/emboss_text_util.h" +#include "public/emboss_view_parameters.h" + +namespace emboss { +namespace support { + +// EnumView is a view for Enums inside of bitfields. +template <class Enum, class Parameters, class BitViewType> +class EnumView final { + public: + using ValueType = typename ::std::remove_cv<Enum>::type; + static_assert( + Parameters::kBits <= sizeof(ValueType) * 8, + "EnumView requires sizeof(ValueType) * 8 >= Parameters::kBits."); + template <typename... Args> + explicit EnumView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {} + EnumView() : buffer_() {} + EnumView(const EnumView &) = default; + EnumView(EnumView &&) = default; + EnumView &operator=(const EnumView &) = default; + EnumView &operator=(EnumView &&) = default; + ~EnumView() = default; + + // TODO(bolms): Here and in CouldWriteValue(), the static_casts to ValueType + // rely on implementation-defined behavior when ValueType is signed. + ValueType Read() const { + ValueType result = static_cast<ValueType>(buffer_.ReadUInt()); + EMBOSS_CHECK(Parameters::ValueIsOk(result)); + return result; + } + ValueType UncheckedRead() const { + return static_cast<ValueType>(buffer_.UncheckedReadUInt()); + } + void Write(ValueType value) const { EMBOSS_CHECK(TryToWrite(value)); } + bool TryToWrite(ValueType value) const { + if (!CouldWriteValue(value)) return false; + if (!IsComplete()) return false; + buffer_.WriteUInt(static_cast<typename BitViewType::ValueType>(value)); + return true; + } + static constexpr bool CouldWriteValue(ValueType value) { + // The value can be written if: + // + // a) it can fit in BitViewType::ValueType (verified by casting to + // BitViewType::ValueType and back, and making sure that the value is + // unchanged) + // + // and either: + // + // b1) the field size is large enough to hold all values, or + // b2) the value is less than 2**(field size in bits) + return value == static_cast<ValueType>( + static_cast<typename BitViewType::ValueType>(value)) && + ((Parameters::kBits == + sizeof(typename BitViewType::ValueType) * 8) || + (static_cast<typename BitViewType::ValueType>(value) < + ((static_cast<typename BitViewType::ValueType>(1) + << (Parameters::kBits - 1)) + << 1))) && + Parameters::ValueIsOk(value); + } + void UncheckedWrite(ValueType value) const { + buffer_.UncheckedWriteUInt( + static_cast<typename BitViewType::ValueType>(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 EnumView<Enum, Parameters, OtherBitViewType> &other) const { + return Read() == other.Read(); + } + template <class OtherBitViewType> + bool UncheckedEquals( + const EnumView<Enum, 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 { + ::std::string token; + if (!ReadToken(stream, &token)) return false; + if (token.empty()) return false; + if (::std::isdigit(token[0])) { + ::std::uint64_t value; + if (!DecodeInteger(token, &value)) return false; + // TODO(bolms): Fix the static_cast<ValueType> for signed ValueType. + // TODO(bolms): Should values between 2**63 and 2**64-1 actually be + // allowed in the text format when ValueType is signed? + return TryToWrite(static_cast<ValueType>(value)); + } else if (token[0] == '-') { + ::std::int64_t value; + if (!DecodeInteger(token, &value)) return false; + return TryToWrite(static_cast<ValueType>(value)); + } else { + ValueType value; + if (!TryToGetEnumFromName(token.c_str(), &value)) return false; + return TryToWrite(value); + } + } + + template <class Stream> + void WriteToTextStream(Stream *stream, + const TextOutputOptions &options) const { + ::emboss::support::WriteEnumViewToTextStream(this, stream, options); + } + + static constexpr int SizeInBits() { return Parameters::kBits; } + + private: + BitViewType buffer_; +}; + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_ENUM_VIEW_H_
diff --git a/public/emboss_enum_view_test.cc b/public/emboss_enum_view_test.cc new file mode 100644 index 0000000..c6c90d4 --- /dev/null +++ b/public/emboss_enum_view_test.cc
@@ -0,0 +1,275 @@ +// 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. + +#include "public/emboss_enum_view.h" + +#include "public/emboss_prelude.h" +#include <gtest/gtest.h> + +namespace emboss { +namespace support { +namespace test { + +template <size_t kBits> +using LittleEndianBitBlockN = + BitBlock<LittleEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>; + +enum class Foo : int64_t { + kMin = -0x7fffffffffffffffL - 1, + kOne = 1, + kTwo = 2, + kBig = 0x0e0f10, + kBigBackwards = 0x100f0e, + kReallyBig = 0x090a0b0c0d0e0f10L, + kReallyBigBackwards = 0x100f0e0d0c0b0a09L, + k2to24MinusOne = (1L << 24) - 1, + k2to24 = 1L << 24, + kMax = 0x7fffffffffffffffL, +}; + +static inline bool TryToGetEnumFromName(const char *name, Foo *result) { + if (!strcmp("kMin", name)) { + *result = Foo::kMin; + return true; + } + if (!strcmp("kOne", name)) { + *result = Foo::kOne; + return true; + } + if (!strcmp("kTwo", name)) { + *result = Foo::kTwo; + return true; + } + if (!strcmp("kBig", name)) { + *result = Foo::kBig; + return true; + } + if (!strcmp("kBigBackwards", name)) { + *result = Foo::kBigBackwards; + return true; + } + if (!strcmp("kReallyBig", name)) { + *result = Foo::kReallyBig; + return true; + } + if (!strcmp("kReallyBigBackwards", name)) { + *result = Foo::kReallyBigBackwards; + return true; + } + if (!strcmp("k2to24MinusOne", name)) { + *result = Foo::k2to24MinusOne; + return true; + } + if (!strcmp("k2to24", name)) { + *result = Foo::k2to24; + return true; + } + if (!strcmp("kMax", name)) { + *result = Foo::kMax; + return true; + } + return false; +} + +static inline const char *TryToGetNameFromEnum(Foo value) { + switch (value) { + case Foo::kMin: + return "kMin"; + case Foo::kOne: + return "kOne"; + case Foo::kTwo: + return "kTwo"; + case Foo::kBig: + return "kBig"; + case Foo::kBigBackwards: + return "kBigBackwards"; + case Foo::kReallyBig: + return "kReallyBig"; + case Foo::kReallyBigBackwards: + return "kReallyBigBackwards"; + case Foo::k2to24MinusOne: + return "k2to24MinusOne"; + case Foo::k2to24: + return "k2to24"; + case Foo::kMax: + return "kMax"; + default: + return nullptr; + } +} + +template <size_t kBits> +using FooViewN = EnumView<Foo, FixedSizeViewParameters<kBits, AllValuesAreOk>, + LittleEndianBitBlockN<kBits>>; + +template <int kMaxBits> +void CheckEnumViewSizeInBits() { + const int size_in_bits = + EnumView<Foo, FixedSizeViewParameters<kMaxBits, AllValuesAreOk>, + OffsetBitBlock<LittleEndianBitBlockN<64>>>::SizeInBits(); + EXPECT_EQ(size_in_bits, kMaxBits); + return CheckEnumViewSizeInBits<kMaxBits - 1>(); +} + +template <> +void CheckEnumViewSizeInBits<0>() { + return; +} + +TEST(EnumView, SizeInBits) { CheckEnumViewSizeInBits<64>(); } + +TEST(EnumView, ValueType) { + using BitBlockType = OffsetBitBlock<LittleEndianBitBlockN<64>>; + EXPECT_TRUE( + (::std::is_same<Foo, + EnumView<Foo, FixedSizeViewParameters<8, AllValuesAreOk>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<Foo, + EnumView<Foo, FixedSizeViewParameters<6, AllValuesAreOk>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<Foo, + EnumView<Foo, FixedSizeViewParameters<33, AllValuesAreOk>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<Foo, + EnumView<Foo, FixedSizeViewParameters<64, AllValuesAreOk>, + BitBlockType>::ValueType>::value)); +} + +TEST(EnumView, CouldWriteValue) { + EXPECT_TRUE(FooViewN<64>::CouldWriteValue(Foo::kMax)); + EXPECT_TRUE(FooViewN<64>::CouldWriteValue(Foo::kMax)); + EXPECT_TRUE(FooViewN<24>::CouldWriteValue(Foo::k2to24MinusOne)); + EXPECT_FALSE(FooViewN<24>::CouldWriteValue(Foo::k2to24)); +} + +TEST(EnumView, ReadAndWriteWithSufficientBuffer) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}; + auto enum64_view = FooViewN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}; + EXPECT_EQ(Foo::kReallyBig, enum64_view.Read()); + EXPECT_EQ(Foo::kReallyBig, enum64_view.UncheckedRead()); + enum64_view.Write(Foo::kReallyBigBackwards); + EXPECT_EQ((::std::vector<uint8_t>{0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x08}), + bytes); + enum64_view.UncheckedWrite(Foo::kReallyBig); + EXPECT_EQ((::std::vector<uint8_t>{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, + 0x09, 0x08}), + bytes); + EXPECT_TRUE(enum64_view.TryToWrite(Foo::kReallyBigBackwards)); + EXPECT_EQ((::std::vector<uint8_t>{0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x08}), + bytes); + EXPECT_TRUE(enum64_view.Ok()); + EXPECT_TRUE(enum64_view.IsComplete()); +} + +TEST(EnumView, ReadAndWriteWithInsufficientBuffer) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}; + auto enum64_view = FooViewN<64>{ReadWriteContiguousBuffer{bytes.data(), 4}}; + EXPECT_DEATH(enum64_view.Read(), ""); + EXPECT_EQ(Foo::kReallyBig, enum64_view.UncheckedRead()); + EXPECT_DEATH(enum64_view.Write(Foo::kReallyBigBackwards), ""); + EXPECT_FALSE(enum64_view.TryToWrite(Foo::kReallyBigBackwards)); + EXPECT_EQ((::std::vector<uint8_t>{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, + 0x09, 0x08}), + bytes); + enum64_view.UncheckedWrite(Foo::kReallyBigBackwards); + EXPECT_EQ((::std::vector<uint8_t>{0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x08}), + bytes); + EXPECT_FALSE(enum64_view.Ok()); + EXPECT_FALSE(enum64_view.IsComplete()); +} + +TEST(EnumView, NonPowerOfTwoSize) { + ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}}; + auto enum24_view = FooViewN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}; + EXPECT_EQ(Foo::kBig, enum24_view.Read()); + EXPECT_EQ(Foo::kBig, enum24_view.UncheckedRead()); + enum24_view.Write(Foo::kBigBackwards); + EXPECT_EQ((::std::vector<uint8_t>{0x0e, 0x0f, 0x10, 0x0d}), bytes); + EXPECT_DEATH(enum24_view.Write(Foo::k2to24), ""); + enum24_view.UncheckedWrite(Foo::k2to24); + EXPECT_EQ((::std::vector<uint8_t>{0x00, 0x00, 0x00, 0x0d}), bytes); + EXPECT_TRUE(enum24_view.Ok()); + EXPECT_TRUE(enum24_view.IsComplete()); +} + +TEST(EnumView, NonPowerOfTwoSizeInsufficientBuffer) { + ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}}; + auto enum24_view = FooViewN<24>{ReadWriteContiguousBuffer{bytes.data(), 2}}; + EXPECT_DEATH(enum24_view.Read(), ""); + EXPECT_EQ(Foo::kBig, enum24_view.UncheckedRead()); + EXPECT_DEATH(enum24_view.Write(Foo::kBigBackwards), ""); + enum24_view.UncheckedWrite(Foo::kBigBackwards); + EXPECT_EQ((::std::vector<uint8_t>{0x0e, 0x0f, 0x10, 0x0d}), bytes); + EXPECT_FALSE(enum24_view.Ok()); + EXPECT_FALSE(enum24_view.IsComplete()); +} + +TEST(EnumView, UpdateFromText) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}; + const auto enum64_view = + FooViewN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}; + EXPECT_TRUE(UpdateFromText(enum64_view, "kBig")); + EXPECT_EQ(Foo::kBig, enum64_view.Read()); + EXPECT_TRUE(UpdateFromText(enum64_view, "k2to24")); + EXPECT_EQ(Foo::k2to24, enum64_view.Read()); + EXPECT_FALSE(UpdateFromText(enum64_view, "k2to24M")); + EXPECT_EQ(Foo::k2to24, enum64_view.Read()); + EXPECT_TRUE(UpdateFromText(enum64_view, "k2to24MinusOne")); + EXPECT_EQ(Foo::k2to24MinusOne, enum64_view.Read()); + EXPECT_TRUE(UpdateFromText(enum64_view, "0x0e0f10")); + EXPECT_EQ(Foo::kBig, enum64_view.Read()); + EXPECT_TRUE(UpdateFromText(enum64_view, "0x7654321")); + EXPECT_EQ(static_cast<Foo>(0x7654321), enum64_view.Read()); + EXPECT_FALSE(UpdateFromText(enum64_view, "0y0")); + EXPECT_EQ(static_cast<Foo>(0x7654321), enum64_view.Read()); + EXPECT_FALSE(UpdateFromText(enum64_view, "-x")); + EXPECT_EQ(static_cast<Foo>(0x7654321), enum64_view.Read()); + EXPECT_TRUE(UpdateFromText(enum64_view, "-0x8000_0000_0000_0000")); + EXPECT_EQ(Foo::kMin, enum64_view.Read()); +} + +TEST(EnumView, WriteToText) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}; + const auto enum64_view = + FooViewN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}; + EXPECT_EQ("kReallyBig", WriteToString(enum64_view)); + EXPECT_EQ("kReallyBig # 651345242494996240", + WriteToString(enum64_view, TextOutputOptions().WithComments(true))); + EXPECT_EQ("kReallyBig # 0x90a0b0c0d0e0f10", + WriteToString( + enum64_view, + TextOutputOptions().WithComments(true).WithNumericBase(16))); + enum64_view.Write(static_cast<Foo>(123)); + EXPECT_EQ("123", WriteToString(enum64_view)); + EXPECT_EQ("123", + WriteToString(enum64_view, TextOutputOptions().WithComments(true))); + EXPECT_EQ("0x7b", + WriteToString( + enum64_view, + TextOutputOptions().WithComments(true).WithNumericBase(16))); +} + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_maybe.h b/public/emboss_maybe.h new file mode 100644 index 0000000..a11ba39 --- /dev/null +++ b/public/emboss_maybe.h
@@ -0,0 +1,76 @@ +// 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. + +// Definition of the Maybe<T> template class. +#ifndef EMBOSS_PUBLIC_EMBOSS_MAYBE_H_ +#define EMBOSS_PUBLIC_EMBOSS_MAYBE_H_ + +#include <utility> + +#include "public/emboss_defines.h" + +namespace emboss { +// TODO(bolms): Should Maybe be a public type (i.e., live in ::emboss)? +namespace support { + +// Maybe<T> is similar to, but much more restricted than, C++17's std::optional. +// It is intended for use in Emboss's expression system, wherein a non-Known() +// Maybe<T> will usually (but not always) poison the result of an operation. +// +// As such, Maybe<> is intended for use with small, copyable T's: specifically, +// integers, enums, and booleans. It may not perform well with other types. +template <typename T> +class Maybe final { + public: + constexpr Maybe() : value_(), known_(false) {} + constexpr explicit Maybe(T value) + : value_(::std::move(value)), known_(true) {} + constexpr Maybe(const Maybe<T> &) = default; + ~Maybe() = default; + Maybe &operator=(const Maybe &) = default; + Maybe &operator=(T value) { + value_ = ::std::move(value); + known_ = true; + return *this; + } + Maybe &operator=(const T &value) { + value_ = value; + known_ = true; + return *this; + } + + constexpr bool Known() const { return known_; } + T Value() const { + EMBOSS_CHECK(Known()); + return value_; + } + constexpr T ValueOr(T default_value) const { + return known_ ? value_ : default_value; + } + // A non-Ok() Maybe value-initializes value_ to a default (by explicitly + // calling the nullary constructor on value_ in the initializer list), so it + // is safe to just return value_ here. For integral types and enums, value_ + // will be 0, for bool it will be false, and for other types it depends on the + // constructor's behavior. + constexpr T ValueOrDefault() const { return value_; } + + private: + T value_; + bool known_; +}; + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_MAYBE_H_
diff --git a/public/emboss_maybe_test.cc b/public/emboss_maybe_test.cc new file mode 100644 index 0000000..acbf917 --- /dev/null +++ b/public/emboss_maybe_test.cc
@@ -0,0 +1,61 @@ +// 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. + +#include "public/emboss_maybe.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +namespace emboss { +namespace support { +namespace test { + +enum class Foo : ::std::int64_t { + BAR = 1, + BAZ = 2, +}; + +TEST(Maybe, Known) { + EXPECT_TRUE(Maybe<int>(10).Known()); + EXPECT_EQ(10, Maybe<int>(10).ValueOr(3)); + EXPECT_EQ(10, Maybe<int>(10).ValueOrDefault()); + EXPECT_EQ(10, Maybe<int>(10).Value()); + EXPECT_TRUE(Maybe<bool>(true).Value()); + EXPECT_EQ(Foo::BAZ, Maybe<Foo>(Foo::BAZ).ValueOrDefault()); + + Maybe<int> x = Maybe<int>(1000); + Maybe<int> y = Maybe<int>(); + y = x; + EXPECT_TRUE(y.Known()); + EXPECT_EQ(1000, y.Value()); +} + +TEST(Maybe, Unknown) { + EXPECT_FALSE(Maybe<int>().Known()); + EXPECT_EQ(3, Maybe<int>().ValueOr(3)); + EXPECT_EQ(0, Maybe<int>().ValueOrDefault()); + EXPECT_FALSE(Maybe<bool>().ValueOrDefault()); + EXPECT_DEATH(Maybe<int>().Value(), "Known()"); + EXPECT_FALSE(Maybe<bool>().ValueOrDefault()); + EXPECT_EQ(static_cast<Foo>(0), Maybe<Foo>().ValueOrDefault()); + + Maybe<int> x = Maybe<int>(); + Maybe<int> y = Maybe<int>(1000); + y = x; + EXPECT_FALSE(y.Known()); +} + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_memory_util.h b/public/emboss_memory_util.h new file mode 100644 index 0000000..eb91138 --- /dev/null +++ b/public/emboss_memory_util.h
@@ -0,0 +1,968 @@ +// 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. + +// Utilities for efficiently reading and writing to/from memory. +#ifndef EMBOSS_PUBLIC_EMBOSS_MEMORY_UTIL_H_ +#define EMBOSS_PUBLIC_EMBOSS_MEMORY_UTIL_H_ + +#include <algorithm> +#include <cstddef> +#include <cstring> + +#include "public/emboss_bit_util.h" +#include "public/emboss_cpp_types.h" +#include "public/emboss_defines.h" + +namespace emboss { +namespace support { + +// MemoryAccessor reads and writes big- and little-endian unsigned integers in +// and out of memory, using optimized routines where possible. +// +// The default MemoryAccessor just proxies to the MemoryAccessor with the +// next-smallest alignment and equivalent offset: MemoryAccessor<C, 8, 0, 32> +// and MemoryAccessor<C, 8, 4, 32> will proxy to MemoryAccessor<C, 4, 0, 32>, +// since an 8-byte-aligned pointer is also 4-byte-aligned, as is a pointer that +// is 4 bytes away from 8-byte alignment. +template <typename CharT, ::std::size_t kAlignment, ::std::size_t kOffset, + ::std::size_t kBits> +struct MemoryAccessor { + static_assert(IsPowerOfTwo(kAlignment), + "MemoryAccessor requires power-of-two alignment."); + static_assert( + kOffset < kAlignment, + "MemoryAccessor requires offset to be strictly less than alignment."); + + using ChainedAccessor = + MemoryAccessor<CharT, kAlignment / 2, kOffset % (kAlignment / 2), kBits>; + using Unsigned = typename LeastWidthInteger<kBits>::Unsigned; + static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) { + return ChainedAccessor::ReadLittleEndianUInt(bytes); + } + static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) { + ChainedAccessor::WriteLittleEndianUInt(bytes, value); + } + static inline Unsigned ReadBigEndianUInt(const CharT *bytes) { + return ChainedAccessor::ReadBigEndianUInt(bytes); + } + static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) { + ChainedAccessor::WriteBigEndianUInt(bytes, value); + } +}; + +// The least-aligned case for MemoryAccessor is 8-bit alignment, and the default +// version of MemoryAccessor will devolve to this one if there is no more +// specific override. +// +// If the system byte order is known, then these routines can use memcpy and +// (possibly) a byte swap; otherwise they can read individual bytes and +// shift+or them together in the appropriate order. I (bolms@) haven't found a +// compiler that will optimize the multiple reads, shifts, and ors into a single +// read, so the memcpy version is *much* faster for 32-bit and larger reads. +template <typename CharT, ::std::size_t kBits> +struct MemoryAccessor<CharT, 1, 0, kBits> { + static_assert(kBits % 8 == 0, + "MemoryAccessor can only read and write whole-byte values."); + static_assert(IsChar<CharT>::value, + "MemoryAccessor can only be used on pointers to char types."); + + using Unsigned = typename LeastWidthInteger<kBits>::Unsigned; + +#if defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) + static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) { + Unsigned result = 0; + ::std::memcpy(&result, bytes, kBits / 8); + return EMBOSS_LITTLE_ENDIAN_TO_NATIVE(result); + } +#else + static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) { + Unsigned result = 0; + for (decltype(kBits) i = 0; i < kBits / 8; ++i) { + result |= static_cast<Unsigned>(static_cast<uint8_t>(bytes[i])) << i * 8; + } + return result; + } +#endif + +#if defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) + static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) { + value = EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value); + ::std::memcpy(bytes, &value, kBits / 8); + } +#else + static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) { + for (decltype(kBits) i = 0; i < kBits / 8; ++i) { + bytes[i] = static_cast<CharT>(static_cast<uint8_t>(value)); + if (sizeof value > 1) { + // Shifting an 8-bit type by 8 bits is undefined behavior, so skip this + // step for uint8_t. + value >>= 8; + } + } + } +#endif + +#if defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) + static inline Unsigned ReadBigEndianUInt(const CharT *bytes) { + Unsigned result = 0; + // When a big-endian source integer is smaller than the result, the source + // bytes must be copied into the final bytes of the destination. This is + // true whether the host is big- or little-endian. + // + // For a little-endian host: + // + // source (big-endian value 0x112233): + // + // byte 0 byte 1 byte 2 + // +--------+--------+--------+ + // | 0x11 | 0x22 | 0x33 | + // +--------+--------+--------+ + // + // result after memcpy (host-interpreted value 0x33221100): + // + // byte 0 byte 1 byte 2 byte 3 + // +--------+--------+--------+--------+ + // | 0x00 | 0x11 | 0x22 | 0x33 | + // +--------+--------+--------+--------+ + // + // result after 32-bit byte swap (host-interpreted value 0x112233): + // + // byte 0 byte 1 byte 2 byte 3 + // +--------+--------+--------+--------+ + // | 0x33 | 0x22 | 0x11 | 0x00 | + // +--------+--------+--------+--------+ + // + // For a big-endian host: + // + // source (value 0x112233): + // + // byte 0 byte 1 byte 2 + // +--------+--------+--------+ + // | 0x11 | 0x22 | 0x33 | + // +--------+--------+--------+ + // + // result after memcpy (value 0x112233) -- no byte swap needed: + // + // byte 0 byte 1 byte 2 byte 3 + // +--------+--------+--------+--------+ + // | 0x00 | 0x11 | 0x22 | 0x33 | + // +--------+--------+--------+--------+ + ::std::memcpy(reinterpret_cast<char *>(&result) + sizeof result - kBits / 8, + bytes, kBits / 8); + result = EMBOSS_BIG_ENDIAN_TO_NATIVE(result); + return result; + } +#else + static inline Unsigned ReadBigEndianUInt(const CharT *bytes) { + Unsigned result = 0; + for (decltype(kBits) i = 0; i < kBits / 8; ++i) { + result |= static_cast<Unsigned>(static_cast</**/::std::uint8_t>(bytes[i])) + << (kBits - 8 - i * 8); + } + return result; + } +#endif + +#if defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) + static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) { + value = EMBOSS_NATIVE_TO_BIG_ENDIAN(value); + ::std::memcpy(bytes, + reinterpret_cast<char *>(&value) + sizeof value - kBits / 8, + kBits / 8); + } +#else + static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) { + for (decltype(kBits) i = 0; i < kBits / 8; ++i) { + bytes[kBits / 8 - 1 - i] = + static_cast<CharT>(static_cast</**/ ::std::uint8_t>(value)); + if (sizeof value > 1) { + // Shifting an 8-bit type by 8 bits is undefined behavior, so skip this + // step for uint8_t. + value >>= 8; + } + } + } +#endif +}; + +// Specialization of UIntMemoryAccessor for 16- 32- and 64-bit-aligned reads and +// writes, using EMBOSS_ALIAS_SAFE_POINTER_CAST instead of memcpy. +#if defined(EMBOSS_ALIAS_SAFE_POINTER_CAST) && \ + defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) && \ + defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) && \ + defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) && \ + defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) +template <typename CharT> +struct MemoryAccessor<CharT, 8, 0, 64> { + static inline ::std::uint64_t ReadLittleEndianUInt(const CharT *bytes) { + return EMBOSS_LITTLE_ENDIAN_TO_NATIVE( + *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint64_t, bytes)); + } + + static inline void WriteLittleEndianUInt(CharT *bytes, + ::std::uint64_t value) { + *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint64_t, bytes) = + EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value); + } + + static inline ::std::uint64_t ReadBigEndianUInt(const CharT *bytes) { + return EMBOSS_BIG_ENDIAN_TO_NATIVE( + *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint64_t, bytes)); + } + + static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint64_t value) { + *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint64_t, bytes) = + EMBOSS_NATIVE_TO_BIG_ENDIAN(value); + } +}; + +template <typename CharT> +struct MemoryAccessor<CharT, 4, 0, 32> { + static inline ::std::uint32_t ReadLittleEndianUInt(const CharT *bytes) { + return EMBOSS_LITTLE_ENDIAN_TO_NATIVE( + *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint32_t, bytes)); + } + + static inline void WriteLittleEndianUInt(CharT *bytes, + ::std::uint32_t value) { + *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint32_t, bytes) = + EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value); + } + + static inline ::std::uint32_t ReadBigEndianUInt(const CharT *bytes) { + return EMBOSS_BIG_ENDIAN_TO_NATIVE( + *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint32_t, bytes)); + } + + static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint32_t value) { + *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint32_t, bytes) = + EMBOSS_NATIVE_TO_BIG_ENDIAN(value); + } +}; + +template <typename CharT> +struct MemoryAccessor<CharT, 2, 0, 16> { + static inline ::std::uint16_t ReadLittleEndianUInt(const CharT *bytes) { + return EMBOSS_LITTLE_ENDIAN_TO_NATIVE( + *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint16_t, bytes)); + } + + static inline void WriteLittleEndianUInt(CharT *bytes, + ::std::uint16_t value) { + *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint16_t, bytes) = + EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value); + } + + static inline ::std::uint16_t ReadBigEndianUInt(const CharT *bytes) { + return EMBOSS_BIG_ENDIAN_TO_NATIVE( + *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint16_t, bytes)); + } + + static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint16_t value) { + *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint16_t, bytes) = + EMBOSS_NATIVE_TO_BIG_ENDIAN(value); + } +}; +#endif // defined(EMBOSS_ALIAS_SAFE_POINTER_CAST) && + // defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) && + // defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) && + // defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) && + // defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) + +// This is the Euclidean GCD algorithm, in C++11-constexpr-safe form. The +// initial is-b-greater-than-a-if-so-swap is omitted, since gcd(b % a, a) is the +// same as gcd(b, a) when a > b. +inline constexpr ::std::size_t GreatestCommonDivisor(::std::size_t a, + ::std::size_t b) { + return a == 0 ? b : GreatestCommonDivisor(b % a, a); +} + +// ContiguousBuffer is a direct view of a fixed number of contiguous bytes in +// memory. If Byte is a const type, it will be a read-only view; if Byte is +// non-const, then writes will be allowed. +// +// The kAlignment and kOffset parameters are used to optimize certain reads and +// writes. static_cast<uintptr_t>(bytes_) % kAlignment must equal kOffset. +// +// This class is used extensively by generated code, and is not intended to be +// heavily used by hand-written code -- some interfaces can be tricky to call +// correctly. +template <typename Byte, ::std::size_t kAlignment, ::std::size_t kOffset> +class ContiguousBuffer final { + // There aren't many systems with non-8-bit chars, and a quirk of POSIX + // requires that POSIX C systems have CHAR_BIT == 8, but some DSPs use wider + // chars. + static_assert(CHAR_BIT == 8, "ContiguousBuffer requires 8-bit chars."); + + // ContiguousBuffer assumes that its backing store is byte-oriented. The + // previous check ensures that chars are 8 bits, and this one ensures that the + // backing store uses chars. + // + // Note that this check is explicitly that Byte is one of the three standard + // char types, and not that (say) it is a one-byte type with an assignment + // operator that can be static_cast<> to and from uint8_t. I (bolms@) have + // chosen to lock it down to just char types to avoid running afoul of strict + // aliasing rules anywhere. + // + // Of somewhat academic interest, uint8_t is not required to be a char type + // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66110#c10), though it is + // unlikely that any compiler vendor will actually change it, as there is + // probably enough real-world code that relies on uint8_t being allowed to + // alias. + static_assert(IsChar<Byte>::value, "ContiguousBuffer requires char type."); + + // Because real-world processors only care about power-of-2 alignments, + // ContiguousBuffer only supports power-of-2 alignments. Note that + // GetOffsetStorage can handle non-power-of-2 alignments. + static_assert(IsPowerOfTwo(kAlignment), + "ContiguousBuffer requires power-of-two alignment."); + + // To avoid template variant explosion, ContiguousBuffer requires kOffset to + // be strictly less than kAlignment. Users of ContiguousBuffer are expected + // to take the modulus of kOffset by kAlignment before passing it in as a + // parameter. + static_assert( + kOffset < kAlignment, + "ContiguousBuffer requires offset to be strictly less than alignment."); + + public: + using ByteType = Byte; + // OffsetStorageType<kSubAlignment, kSubOffset> is the return type of + // GetOffsetStorage<kSubAlignment, kSubOffset>(...). This is used in a number + // of places in generated code to specify deeply-nested template values. + // + // In theory, anything that cared about this type could use + // decltype(declval(ContiguousBuffer<...>).GetOffsetStorage<kSubAlignment, + // kSubOffset>(0, 0)) instead, but that is much more cumbersome, and it + // appears that at least some versions of GCC do not handle it correctly. + template </**/ ::std::size_t kSubAlignment, ::std::size_t kSubOffset> + using OffsetStorageType = + ContiguousBuffer<Byte, GreatestCommonDivisor(kAlignment, kSubAlignment), + (kOffset + kSubOffset) % + GreatestCommonDivisor(kAlignment, kSubAlignment)>; + + // Constructs a default ContiguousBuffer. + ContiguousBuffer() : bytes_(nullptr), size_(0) {} + + // Constructs a ContiguousBuffer from a contiguous container type over some + // `char` type, such as std::string, std::vector<signed char>, + // std::array<unsigned char, N>, or std::string_view. + // + // This template is only enabled if: + // + // 1. bytes->data() returns a pointer to some char type. + // 2. Byte is at least as cv-qualified as decltype(*bytes->data()). + // + // The first requirement means that this constructor won't work on, e.g., + // std::vector<int> -- this is mostly a precautionary measure, since + // ContiguousBuffer only uses alias-safe operations anyway. + // + // The second requirement means that const and volatile are respected in the + // expected way: a ContiguousBuffer<const unsigned char, ...> may be + // initialized from std::vector<char>, but a ContiguousBuffer<unsigned char, + // ...> may not be initialized from std::string_view. + template < + typename T, + typename = typename ::std::enable_if< + IsChar<typename ::std::remove_cv<typename ::std::remove_reference< + decltype(*(::std::declval<T>().data()))>::type>::type>::value + && ::std::is_same< + typename AddSourceCV<decltype(*::std::declval<T>().data()), + Byte>::Type, + Byte>::value>::type> + explicit ContiguousBuffer(T *bytes) + : bytes_{reinterpret_cast<Byte *>(bytes->data())}, size_{bytes->size()} { + if (bytes != nullptr) + EMBOSS_DCHECK_POINTER_ALIGNMENT(bytes, kAlignment, kOffset); + } + + // Constructs a ContiguousBuffer from a pointer to a char type and a size. As + // with the constructor from a container, above, Byte must be at least as + // cv-qualified as T. + template < + typename T, + typename = typename ::std::enable_if<IsChar<T>::value && ::std::is_same< + typename AddSourceCV<T, Byte>::Type, Byte>::value>> + explicit ContiguousBuffer(T *bytes, ::std::size_t size) + : bytes_{reinterpret_cast<Byte *>(bytes)}, + size_{bytes == nullptr ? 0 : size} { + if (bytes != nullptr) + EMBOSS_DCHECK_POINTER_ALIGNMENT(bytes, kAlignment, kOffset); + } + + // Constructs a ContiguousBuffer from nullptr. Equivalent to + // ContiguousBuffer(). + // + // TODO(bolms): Update callers and remove this constructor. + explicit ContiguousBuffer(::std::nullptr_t) : bytes_{nullptr}, size_{0} {} + + // Implicitly constructs a ContiguousBuffer from an identical + // ContiguousBuffer. + ContiguousBuffer(const ContiguousBuffer &other) = default; + + // Explicitly construct a ContiguousBuffers from another, compatible + // ContiguousBuffer. A compatible ContiguousBuffer has an + // equally-or-less-cv-qualified Byte type, an alignment that is an exact + // multiple of this ContiguousBuffer's alignment, and an offset that is the + // same when reduced to this ContiguousBuffer's alignment. + // + // The final !::std::is_same<...> clause prevents this constructor from + // overlapping with the *implicit* copy constructor. + template < + typename OtherByte, size_t kOtherAlignment, size_t kOtherOffset, + typename = typename ::std::enable_if< + kOtherAlignment % kAlignment == 0 && + kOtherOffset % kAlignment == + kOffset && ::std::is_same< + typename AddSourceCV<OtherByte, Byte>::Type, Byte>::value && + !::std::is_same<ContiguousBuffer, + ContiguousBuffer<OtherByte, kOtherAlignment, + kOtherOffset>>::value>::type> + explicit ContiguousBuffer( + const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other) + : bytes_{reinterpret_cast<Byte *>(other.data())}, + size_{other.SizeInBytes()} {} + + // Assignment from a compatible ContiguousBuffer. + template <typename OtherByte, size_t kOtherAlignment, size_t kOtherOffset, + typename = typename ::std::enable_if< + kOtherAlignment % kAlignment == 0 && + kOtherOffset % kAlignment == + kOffset && ::std::is_same< + typename AddSourceCV<OtherByte, Byte>::Type, + Byte>::value>::type> + ContiguousBuffer &operator=( + const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other) { + bytes_ = reinterpret_cast<Byte *>(other.data()); + size_ = other.SizeInBytes(); + return *this; + } + + // GetOffsetStorage returns a new ContiguousBuffer that is a subsection of + // this ContiguousBuffer, with appropriate alignment assertions. The new + // ContiguousBuffer will point to a region `offset` bytes into the original + // ContiguousBuffer, with a size of `max(size, original_size - offset)`. + // + // The kSubAlignment and kSubOffset template parameters act as assertions + // about the value of `offset`: `offset % (kSubAlignment / 8) - (kSubOffset / + // 8)` must be zero. That is, if `kSubAlignment` is 16 and `kSubOffset` is 8, + // then `offset` may be 1, 3, 5, 7, etc. + // + // As a special case, if `kSubAlignment` is 0, then `offset` must exactly + // equal `kSubOffset`. + // + // This method is used by generated structure views to get backing buffers for + // views of their fields; the code generator can determine proper values for + // `kSubAlignment` and `kSubOffset`. + template </**/ ::std::size_t kSubAlignment, ::std::size_t kSubOffset> + OffsetStorageType<kSubAlignment, kSubOffset> GetOffsetStorage( + ::std::size_t offset, ::std::size_t size) const { + static_assert(kSubAlignment == 0 || kSubAlignment > kSubOffset, + "kSubAlignment must be greater than kSubOffset."); + // Emboss provides a fast, unchecked path for reads and writes like: + // + // view.field().subfield().UncheckedWrite(). + // + // Each of .field() and .subfield() call GetOffsetStorage(), so + // GetOffsetStorage() must be small and fast. + if (kSubAlignment == 0) { + EMBOSS_DCHECK_EQ(offset, kSubOffset); + } else { + // The weird ?:, below, silences -Werror=div-by-zero on versions of GCC + // that aren't smart enough to figure out that kSubAlignment can't be zero + // in this branch. + EMBOSS_DCHECK_EQ(offset % (kSubAlignment == 0 ? 1 : kSubAlignment), + kSubOffset); + } + using ResultStorageType = OffsetStorageType<kSubAlignment, kSubOffset>; + return bytes_ == nullptr + ? ResultStorageType{nullptr} + : ResultStorageType{ + bytes_ + offset, + size_ < offset ? 0 : ::std::min(size, size_ - offset)}; + } + + // ReadLittleEndianUInt, ReadBigEndianUInt, and the unchecked versions thereof + // provide efficient multibyte read access to the underlying buffer. The + // kBits template parameter should always equal the buffer size when these are + // called. + // + // Generally, types other than unsigned integers can be relatively efficiently + // converted from unsigned integers, and views should use Read...UInt to read + // the raw value, then convert. + // + // Read...UInt always reads the entire buffer; to read a smaller section, use + // GetOffsetStorage first. + template </**/ ::std::size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned ReadLittleEndianUInt() const { + EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits); + EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset); + return UncheckedReadLittleEndianUInt<kBits>(); + } + template </**/ ::std::size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned UncheckedReadLittleEndianUInt() + const { + static_assert(kBits % 8 == 0, + "ContiguousBuffer::ReadLittleEndianUInt() can only read " + "whole-byte values."); + return MemoryAccessor<Byte, kAlignment, kOffset, + kBits>::ReadLittleEndianUInt(bytes_); + } + template </**/ ::std::size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned ReadBigEndianUInt() const { + EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits); + EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset); + return UncheckedReadBigEndianUInt<kBits>(); + } + template </**/ ::std::size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned UncheckedReadBigEndianUInt() + const { + static_assert(kBits % 8 == 0, + "ContiguousBuffer::ReadBigEndianUInt() can only read " + "whole-byte values."); + return MemoryAccessor<Byte, kAlignment, kOffset, kBits>::ReadBigEndianUInt( + bytes_); + } + + // WriteLittleEndianUInt, WriteBigEndianUInt, and the unchecked versions + // thereof provide efficient write access to the buffer. Similar to the Read + // methods above, they write the entire buffer from an unsigned integer; + // non-unsigned values should be converted to the equivalent bit pattern, then + // written, and to write a subsection of the buffer use GetOffsetStorage + // first. + template </**/ ::std::size_t kBits> + void WriteLittleEndianUInt( + typename LeastWidthInteger<kBits>::Unsigned value) const { + EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits); + EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset); + UncheckedWriteLittleEndianUInt<kBits>(value); + } + template </**/ ::std::size_t kBits> + void UncheckedWriteLittleEndianUInt( + typename LeastWidthInteger<kBits>::Unsigned value) const { + static_assert(kBits % 8 == 0, + "ContiguousBuffer::WriteLittleEndianUInt() can only write " + "whole-byte values."); + MemoryAccessor<Byte, kAlignment, kOffset, kBits>::WriteLittleEndianUInt( + bytes_, value); + } + template </**/ ::std::size_t kBits> + void WriteBigEndianUInt( + typename LeastWidthInteger<kBits>::Unsigned value) const { + EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits); + EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset); + return UncheckedWriteBigEndianUInt<kBits>(value); + } + template </**/ ::std::size_t kBits> + void UncheckedWriteBigEndianUInt( + typename LeastWidthInteger<kBits>::Unsigned value) const { + static_assert(kBits % 8 == 0, + "ContiguousBuffer::WriteBigEndianUInt() can only write " + "whole-byte values."); + MemoryAccessor<Byte, kAlignment, kOffset, kBits>::WriteBigEndianUInt(bytes_, + value); + } + + template <typename OtherByte, ::std::size_t kOtherAlignment, + ::std::size_t kOtherOffset> + void UncheckedCopyFrom( + const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other, + ::std::size_t size) const { + memmove(data(), other.data(), size); + } + template <typename OtherByte, ::std::size_t kOtherAlignment, + ::std::size_t kOtherOffset> + void CopyFrom( + const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other, + ::std::size_t size) const { + EMBOSS_CHECK(Ok()); + EMBOSS_CHECK(other.Ok()); + // It is OK if either buffer contains extra bytes that are not being copied. + EMBOSS_CHECK_GE(SizeInBytes(), size); + EMBOSS_CHECK_GE(other.SizeInBytes(), size); + UncheckedCopyFrom(other, size); + } + template <typename OtherByte, ::std::size_t kOtherAlignment, + ::std::size_t kOtherOffset> + bool TryToCopyFrom( + const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other, + ::std::size_t size) const { + if (Ok() && other.Ok() && SizeInBytes() >= size && + other.SizeInBytes() >= size) { + UncheckedCopyFrom(other, size); + return true; + } + return false; + } + ::std::size_t SizeInBytes() const { return size_; } + bool Ok() const { return bytes_ != nullptr; } + Byte *data() const { return bytes_; } + Byte *begin() const { return bytes_; } + Byte *end() const { return bytes_ + size_; } + + private: + Byte *bytes_ = nullptr; + ::std::size_t size_ = 0; +}; + +// TODO(bolms): Remove these aliases. +using ReadWriteContiguousBuffer = ContiguousBuffer<unsigned char, 1, 0>; +using ReadOnlyContiguousBuffer = ContiguousBuffer<const unsigned char, 1, 0>; + +// LittleEndianByteOrderer is a pass-through adapter for a byte buffer class. +// It is used to implement little-endian bit blocks. +// +// When used by BitBlock, the resulting bits are numbered as if they are +// little-endian: +// +// bit addresses of each bit in each byte +// +----+----+----+----+----+----+----+----+----+----+----+----+---- +// bit in 7 | 7 | 15 | 23 | 31 | 39 | 47 | 55 | 63 | 71 | 79 | 87 | 95 | +// byte 6 | 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 | 70 | 78 | 86 | 94 | +// 5 | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 | 69 | 77 | 85 | 93 | +// 4 | 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 | 68 | 76 | 84 | 92 | +// 3 | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 | 67 | 75 | 83 | 91 | ... +// 2 | 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 | 66 | 74 | 82 | 90 | +// 1 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 | 65 | 73 | 81 | 89 | +// 0 | 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | +// +----+----+----+----+----+----+----+----+----+----+----+----+---- +// 0 1 2 3 4 5 6 7 8 9 10 11 ... +// byte address +// +// Because endian-specific reads and writes are handled in ContiguousBuffer, +// this class exists mostly to translate VerbUInt calls to VerbLittleEndianUInt. +template <class BufferT> +class LittleEndianByteOrderer final { + public: + // Type declaration so that BitBlock can use BufferType::BufferType. + using BufferType = BufferT; + + LittleEndianByteOrderer() : buffer_() {} + explicit LittleEndianByteOrderer(BufferType buffer) : buffer_{buffer} {} + LittleEndianByteOrderer(const LittleEndianByteOrderer &other) = default; + LittleEndianByteOrderer(LittleEndianByteOrderer &&other) = default; + LittleEndianByteOrderer &operator=(const LittleEndianByteOrderer &other) = + default; + + // LittleEndianByteOrderer just passes straight through to the underlying + // buffer. + bool Ok() const { return buffer_.Ok(); } + size_t SizeInBytes() const { return buffer_.SizeInBytes(); } + + template <size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const { + return buffer_.template ReadLittleEndianUInt<kBits>(); + } + template <size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const { + return buffer_.template UncheckedReadLittleEndianUInt<kBits>(); + } + template <size_t kBits> + void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const { + buffer_.template WriteLittleEndianUInt<kBits>(value); + } + template <size_t kBits> + void UncheckedWriteUInt( + typename LeastWidthInteger<kBits>::Unsigned value) const { + buffer_.template UncheckedWriteLittleEndianUInt<kBits>(value); + } + + private: + BufferType buffer_; +}; + +// BigEndianByteOrderer is an adapter for a byte buffer class which reverses +// the addresses of the underlying byte buffer. It is used to implement +// big-endian bit blocks. +// +// When used by BitBlock, the resulting bits are numbered with "bit 0" as the +// lowest-order bit of the *last* byte in the buffer. For example, for a +// 12-byte buffer, the bit ordering looks like: +// +// bit addresses of each bit in each byte +// +----+----+----+----+----+----+----+----+----+----+----+----+ +// bit in 7 | 95 | 87 | 79 | 71 | 63 | 55 | 47 | 39 | 31 | 23 | 15 | 7 | +// byte 6 | 94 | 86 | 78 | 70 | 62 | 54 | 46 | 38 | 30 | 22 | 14 | 6 | +// 5 | 93 | 85 | 77 | 69 | 61 | 53 | 45 | 37 | 29 | 21 | 13 | 5 | +// 4 | 92 | 84 | 76 | 68 | 60 | 52 | 44 | 36 | 28 | 20 | 12 | 4 | +// 3 | 91 | 83 | 75 | 67 | 59 | 51 | 43 | 35 | 27 | 19 | 11 | 3 | +// 2 | 90 | 82 | 74 | 66 | 58 | 50 | 42 | 34 | 26 | 18 | 10 | 2 | +// 1 | 89 | 81 | 73 | 65 | 57 | 49 | 41 | 33 | 25 | 17 | 9 | 1 | +// 0 | 88 | 80 | 72 | 64 | 56 | 48 | 40 | 32 | 24 | 16 | 8 | 0 | +// +----+----+----+----+----+----+----+----+----+----+----+----+ +// 0 1 2 3 4 5 6 7 8 9 10 11 +// byte address +// +// Note that some big-endian protocols are documented with "bit 0" being the +// *high-order* bit of a number, in which case "bit 0" would be the +// highest-order bit of the first byte in the buffer. The "bit 0 is the +// high-order bit" style seems to be more common in older documents (e.g., RFCs +// 791 and 793, for IP and TCP), while the Emboss-style "bit 0 is in the last +// byte" seems to be more common in newer documents (e.g., the hardware user +// manuals bolms@ examined). +// TODO(bolms): Examine more documents to see if the old vs new pattern holds. +// +// Because endian-specific reads and writes are handled in ContiguousBuffer, +// this class exists mostly to translate VerbUInt calls to VerbBigEndianUInt. +template <class BufferT> +class BigEndianByteOrderer final { + public: + // Type declaration so that BitBlock can use BufferType::BufferType. + using BufferType = BufferT; + + BigEndianByteOrderer() : buffer_() {} + explicit BigEndianByteOrderer(BufferType buffer) : buffer_{buffer} {} + BigEndianByteOrderer(const BigEndianByteOrderer &other) = default; + BigEndianByteOrderer(BigEndianByteOrderer &&other) = default; + BigEndianByteOrderer &operator=(const BigEndianByteOrderer &other) = default; + + // Ok() and SizeInBytes() get passed through with no changes. + bool Ok() const { return buffer_.Ok(); } + size_t SizeInBytes() const { return buffer_.SizeInBytes(); } + + template <size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const { + return buffer_.template ReadBigEndianUInt<kBits>(); + } + template <size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const { + return buffer_.template UncheckedReadBigEndianUInt<kBits>(); + } + template <size_t kBits> + void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const { + buffer_.template WriteBigEndianUInt<kBits>(value); + } + template <size_t kBits> + void UncheckedWriteUInt( + typename LeastWidthInteger<kBits>::Unsigned value) const { + buffer_.template UncheckedWriteBigEndianUInt<kBits>(value); + } + + private: + BufferType buffer_; +}; + +// NullByteOrderer is a pass-through adapter for a byte buffer class. It is +// used to implement single-byte bit blocks, where byte order does not matter. +// +// Technically, it should be valid to swap in BigEndianByteOrderer or +// LittleEndianByteOrderer anywhere that NullByteOrderer is used, but +// NullByteOrderer contains a few extra CHECKs to ensure it is being used +// correctly. +template <class BufferT> +class NullByteOrderer final { + public: + // Type declaration so that BitBlock can use BufferType::BufferType. + using BufferType = BufferT; + + NullByteOrderer() : buffer_() {} + explicit NullByteOrderer(BufferType buffer) : buffer_{buffer} {} + NullByteOrderer(const NullByteOrderer &other) = default; + NullByteOrderer(NullByteOrderer &&other) = default; + NullByteOrderer &operator=(const NullByteOrderer &other) = default; + + bool Ok() const { return buffer_.Ok(); } + size_t SizeInBytes() const { return Ok() ? 1 : 0; } + + template <size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const { + static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values."); + return buffer_.template ReadLittleEndianUInt<kBits>(); + } + template <size_t kBits> + typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const { + static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values."); + return buffer_.template UncheckedReadLittleEndianUInt<kBits>(); + } + template <size_t kBits> + void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const { + static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values."); + buffer_.template WriteBigEndianUInt<kBits>(value); + } + template <size_t kBits> + void UncheckedWriteUInt( + typename LeastWidthInteger<kBits>::Unsigned value) const { + static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values."); + buffer_.template UncheckedWriteBigEndianUInt<kBits>(value); + } + + private: + BufferType buffer_; +}; + +// OffsetBitBlock is a filter on another BitBlock class, which adds a fixed +// offset to reads from underlying bit block. This is used by Emboss generated +// classes to read bitfields: the parent provides an OffsetBitBlock of its +// buffer to the child's view. +// +// OffsetBitBlock is always statically sized, but because +// BitBlock::GetOffsetStorage and OffsetBitBlock::GetOffsetStorage must have the +// same signature as ContiguousBuffer::GetOffsetStorage, OffsetBitBlock's size +// parameter must be a runtime value. +// +// TODO(bolms): Figure out how to add size as a template parameter to +// OffsetBitBlock. +template <class UnderlyingBitBlockType> +class OffsetBitBlock final { + public: + using ValueType = typename UnderlyingBitBlockType::ValueType; + // Bit blocks do not use alignment information, but generated code expects bit + // blocks to have the same methods and types as byte blocks, so even though + // kNewAlignment and kNewOffset are unused, they must be present as template + // parameters. + template <size_t kNewAlignment, size_t kNewOffset> + using OffsetStorageType = OffsetBitBlock<UnderlyingBitBlockType>; + + OffsetBitBlock() : bit_block_(), offset_(0), size_(0), ok_(false) {} + explicit OffsetBitBlock(UnderlyingBitBlockType bit_block, size_t offset, + size_t size, bool ok) + : bit_block_{bit_block}, + offset_{static_cast<uint8_t>(offset)}, + size_{static_cast<uint8_t>(size)}, + ok_{offset == offset_ && size == size_ && ok} {} + OffsetBitBlock(const OffsetBitBlock &other) = default; + OffsetBitBlock &operator=(const OffsetBitBlock &other) = default; + + template <size_t kNewAlignment, size_t kNewOffset> + OffsetStorageType<kNewAlignment, kNewOffset> GetOffsetStorage( + size_t offset, size_t size) const { + return OffsetStorageType<kNewAlignment, kNewOffset>{ + bit_block_, offset_ + offset, size, ok_ && offset + size <= size_}; + } + + // ReadUInt reads the entire underlying bit block, then shifts and masks to + // the appropriate size. + ValueType ReadUInt() const { + EMBOSS_CHECK_GE(bit_block_.SizeInBits(), offset_ + size_); + EMBOSS_CHECK(Ok()); + return MaskToNBits(bit_block_.ReadUInt(), offset_ + size_) >> offset_; + } + ValueType UncheckedReadUInt() const { + return MaskToNBits(bit_block_.UncheckedReadUInt(), offset_ + size_) >> + offset_; + } + + // WriteUInt writes the entire underlying bit block; in order to only write + // the specific bits that should be changed, the current value is first read, + // then masked out and or'ed with the new value, and finally the result is + // written back to memory. + void WriteUInt(ValueType value) const { + EMBOSS_CHECK_EQ(value, MaskToNBits(value, size_)); + EMBOSS_CHECK(Ok()); + // OffsetBitBlock::WriteUInt *always* does a read-modify-write because it is + // assumed that if the user wanted to read or write the entire value they + // would just use the underlying BitBlock directly. This is mostly true for + // code generated by Emboss, which only uses OffsetBitBlock for subfields of + // `bits` types; bit-oriented types such as `UInt` will use BitBlock + // directly when they are placed directly in a `struct`. + bit_block_.WriteUInt(MaskInValue(bit_block_.ReadUInt(), value)); + } + void UncheckedWriteUInt(ValueType value) const { + bit_block_.UncheckedWriteUInt( + MaskInValue(bit_block_.UncheckedReadUInt(), value)); + } + + size_t SizeInBits() const { return size_; } + bool Ok() const { return ok_; } + + private: + ValueType MaskInValue(ValueType original_value, ValueType new_value) const { + ValueType original_mask = + ~(MaskToNBits(static_cast<ValueType>(~ValueType{0}), size_) << offset_); + return (original_value & original_mask) | (new_value << offset_); + } + + const UnderlyingBitBlockType bit_block_; + const uint8_t offset_; + const uint8_t size_; + const uint8_t ok_; +}; + +// BitBlock is a view of a short, fixed-size sequence of bits somewhere in +// memory. Big- and little-endian values are handled by BufferType, which is +// typically BigEndianByteOrderer<ContiguousBuffer<...>> or +// LittleEndianByteOrderer<ContiguousBuffer<...>>. +// +// BitBlock is implemented such that it always reads and writes its entire +// buffer; unlike ContiguousBuffer for bytes, there is no way to modify part of +// the underlying data without doing a read-modify-write of the full value. +// This sidesteps a lot of weirdness with converting between bit addresses and +// byte addresses for big-endian values, though it does mean that in certain +// cases excess bits will be read or written, particularly if care is not taken +// in the .emb definition to keep `bits` types to a minimum size. +template <class BufferType, size_t kBufferSizeInBits> +class BitBlock final { + static_assert(kBufferSizeInBits % 8 == 0, + "BitBlock can only operate on byte buffers."); + static_assert(kBufferSizeInBits <= 64, + "BitBlock can only operate on small buffers."); + + public: + using ValueType = typename LeastWidthInteger<kBufferSizeInBits>::Unsigned; + // As with OffsetBitBlock::OffsetStorageType, the kNewAlignment and kNewOffset + // values are not used, but they must be template parameters so that generated + // code can work with both BitBlock and ContiguousBuffer. + template <size_t kNewAlignment, size_t kNewOffset> + using OffsetStorageType = + OffsetBitBlock<BitBlock<BufferType, kBufferSizeInBits>>; + + explicit BitBlock() : buffer_() {} + explicit BitBlock(BufferType buffer) : buffer_{buffer} {} + explicit BitBlock(typename BufferType::BufferType buffer) : buffer_{buffer} {} + BitBlock(const BitBlock &) = default; + BitBlock(BitBlock &&) = default; + BitBlock &operator=(const BitBlock &) = default; + BitBlock &operator=(BitBlock &&) = default; + ~BitBlock() = default; + + static constexpr size_t Bits() { return kBufferSizeInBits; } + + template <size_t kNewAlignment, size_t kNewOffset> + OffsetStorageType<kNewAlignment, kNewOffset> GetOffsetStorage( + size_t offset, size_t size) const { + return OffsetStorageType<kNewAlignment, kNewOffset>{ + *this, offset, size, Ok() && offset + size <= kBufferSizeInBits}; + } + + // BitBlock clients must read or write the entire BitBlock value as an + // unsigned integer. OffsetBitBlock can be used to extract a portion of the + // value via shift and mask, and individual view types such as IntView or + // BcdView are expected to convert ValueType to/from their desired types. + ValueType ReadUInt() const { + return buffer_.template ReadUInt<kBufferSizeInBits>(); + } + ValueType UncheckedReadUInt() const { + return buffer_.template UncheckedReadUInt<kBufferSizeInBits>(); + } + void WriteUInt(ValueType value) const { + EMBOSS_CHECK_EQ(value, MaskToNBits(value, kBufferSizeInBits)); + buffer_.template WriteUInt<kBufferSizeInBits>(value); + } + void UncheckedWriteUInt(ValueType value) const { + buffer_.template UncheckedWriteUInt<kBufferSizeInBits>(value); + } + + size_t SizeInBits() const { return kBufferSizeInBits; } + bool Ok() const { + return buffer_.Ok() && buffer_.SizeInBytes() * 8 == kBufferSizeInBits; + } + + private: + BufferType buffer_; +}; + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_MEMORY_UTIL_H_
diff --git a/public/emboss_memory_util_test.cc b/public/emboss_memory_util_test.cc new file mode 100644 index 0000000..9ac20db --- /dev/null +++ b/public/emboss_memory_util_test.cc
@@ -0,0 +1,641 @@ +// 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. + +#include "public/emboss_memory_util.h" + +#include "public/emboss_prelude.h" +#include <gtest/gtest.h> + +namespace emboss { +namespace support { +namespace test { + +using ::emboss::prelude::IntView; +using ::emboss::prelude::UIntView; + +template <size_t kBits> +using BigEndianBitBlockN = + BitBlock<BigEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>; + +template <size_t kBits> +using LittleEndianBitBlockN = + BitBlock<LittleEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>; + +TEST(GreatestCommonDivisor, GreatestCommonDivisor) { + EXPECT_EQ(4, GreatestCommonDivisor(12, 20)); + EXPECT_EQ(4, GreatestCommonDivisor(20, 12)); + EXPECT_EQ(4, GreatestCommonDivisor(20, 4)); + EXPECT_EQ(6, GreatestCommonDivisor(12, 78)); + EXPECT_EQ(6, GreatestCommonDivisor(6, 0)); + EXPECT_EQ(6, GreatestCommonDivisor(0, 6)); + EXPECT_EQ(3, GreatestCommonDivisor(9, 6)); + EXPECT_EQ(0, GreatestCommonDivisor(0, 0)); +} + +// Because MemoryAccessor's parameters are template parameters, it is not +// possible to loop through them directly. Instead, TestMemoryAccessor tests +// a particular MemoryAccessor's methods, then calls the next template to test +// the next set of template parameters to MemoryAccessor. +template <typename CharT, size_t kAlignment, size_t kOffset, size_t kBits> +void TestMemoryAccessor() { + alignas(kAlignment) + CharT bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + EXPECT_EQ( + 0x0807060504030201UL & (~0x0UL >> (64 - kBits)), + (MemoryAccessor<CharT, kAlignment, kOffset, kBits>::ReadLittleEndianUInt( + bytes))) + << "kAlignment = " << kAlignment << "; kOffset = " << kOffset + << "; kBits = " << kBits; + EXPECT_EQ( + 0x0102030405060708UL >> (64 - kBits), + (MemoryAccessor<CharT, kAlignment, kOffset, kBits>::ReadBigEndianUInt( + bytes))) + << "kAlignment = " << kAlignment << "; kOffset = " << kOffset + << "; kBits = " << kBits; + + MemoryAccessor<CharT, kAlignment, kOffset, kBits>::WriteLittleEndianUInt( + bytes, 0x7172737475767778UL & (~0x0UL >> (64 - kBits))); + ::std::vector<CharT> expected_vector_after_write = { + {0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71}}; + for (int i = kBits / 8; i < 8; ++i) { + expected_vector_after_write[i] = i + 1; + } + EXPECT_EQ(expected_vector_after_write, + ::std::vector<CharT>(bytes, bytes + sizeof bytes)) + << "kAlignment = " << kAlignment << "; kOffset = " << kOffset + << "; kBits = " << kBits; + + MemoryAccessor<CharT, kAlignment, kOffset, kBits>::WriteBigEndianUInt( + bytes, 0x7172737475767778UL >> (64 - kBits)); + expected_vector_after_write = { + {0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78}}; + for (int i = kBits / 8; i < 8; ++i) { + expected_vector_after_write[i] = i + 1; + } + EXPECT_EQ(expected_vector_after_write, + ::std::vector<CharT>(bytes, bytes + sizeof bytes)) + << "kAlignment = " << kAlignment << "; kOffset = " << kOffset + << "; kBits = " << kBits; + + // Recursively iterate the template: + // + // For every kAlignment/kOffset pair, check kBits from 64 to 8 in increments + // of 8. + // + // If kBits is 8, reset kBits to 64 and go to the next kAlignment/kOffset + // pair. + // + // For each kAlignment, try all kOffsets from 0 to kAlignment - 1. + // + // If kBits is 8 and kOffset is kAlignment - 1, reset kBits to 64, kOffset to + // 0, and halve kAlignment. + // + // Base cases below handle kAlignment == 0, terminating the recursion. + TestMemoryAccessor< + CharT, + kBits == 8 && kAlignment == kOffset + 1 ? kAlignment / 2 : kAlignment, + kBits == 8 ? kAlignment == kOffset + 1 ? 0 : kOffset + 1 : kOffset, + kBits == 8 ? 64 : kBits - 8>(); +} + +template <> +void TestMemoryAccessor<char, 0, 0, 64>() {} + +template <> +void TestMemoryAccessor<signed char, 0, 0, 64>() {} + +template <> +void TestMemoryAccessor<unsigned char, 0, 0, 64>() {} + +TEST(MemoryAccessor, LittleEndianReads) { + TestMemoryAccessor<char, 8, 0, 64>(); + TestMemoryAccessor<signed char, 8, 0, 64>(); + TestMemoryAccessor<unsigned char, 8, 0, 64>(); +} + +TEST(ContiguousBuffer, OffsetStorageType) { + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 2, 0>, + ContiguousBuffer<char, 2, 0>::OffsetStorageType<2, 0>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 2, 0>, + ContiguousBuffer<char, 2, 0>::OffsetStorageType<0, 0>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 2, 0>, + ContiguousBuffer<char, 2, 0>::OffsetStorageType<4, 0>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 2, 0>, + ContiguousBuffer<char, 4, 0>::OffsetStorageType<2, 0>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 2, 0>, + ContiguousBuffer<char, 4, 2>::OffsetStorageType<2, 0>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 2, 0>, + ContiguousBuffer<char, 4, 1>::OffsetStorageType<2, 1>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 4, 2>, + ContiguousBuffer<char, 4, 1>::OffsetStorageType<4, 1>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 4, 1>, + ContiguousBuffer<char, 4, 3>::OffsetStorageType<0, 2>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 4, 1>, + ContiguousBuffer<char, 4, 3>::OffsetStorageType<4, 2>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 4, 1>, + ContiguousBuffer<char, 4, 3>::OffsetStorageType<8, 6>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 4, 1>, + ContiguousBuffer<char, 4, 3>::OffsetStorageType<12, 6>>::value)); + EXPECT_TRUE((::std::is_same< + ContiguousBuffer<char, 1, 0>, + ContiguousBuffer<char, 4, 1>::OffsetStorageType<3, 1>>::value)); +} + +// Minimal class that forwards to std::allocator. Used to test that +// ReadOnlyContiguousBuffer can be constructed from std::vector<> and +// std::basic_string<> with non-default trailing template parameters. +template <class T> +struct NonstandardAllocator { + using value_type = typename ::std::allocator<T>::value_type; + using pointer = typename ::std::allocator<T>::pointer; + using const_pointer = typename ::std::allocator<T>::const_pointer; + using reference = typename ::std::allocator<T>::reference; + using const_reference = typename ::std::allocator<T>::const_reference; + using size_type = typename ::std::allocator<T>::size_type; + using difference_type = typename ::std::allocator<T>::difference_type; + + template <class U> + struct rebind { + using other = NonstandardAllocator<U>; + }; + + NonstandardAllocator() = default; + // This constructor is *not* explicit in order to conform to the requirements + // for an allocator. + template <class U> + NonstandardAllocator(const NonstandardAllocator<U> &) {} // NOLINT + + T *allocate(size_t n) { return ::std::allocator<T>().allocate(n); } + void deallocate(T *p, size_t n) { ::std::allocator<T>().deallocate(p, n); } + + static size_type max_size() { + return ::std::numeric_limits<size_type>::max() / sizeof(value_type); + } +}; + +template <class T, class U> +bool operator==(const NonstandardAllocator<T> &, + const NonstandardAllocator<U> &) { + return true; +} + +template <class T, class U> +bool operator!=(const NonstandardAllocator<T> &, + const NonstandardAllocator<U> &) { + return false; +} + +// ContiguousBuffer tests for std::vector, std::array, and std::string types. +template <typename T> +class ReadOnlyContiguousBufferTest : public ::testing::Test {}; +typedef ::testing::Types< + /**/ ::std::vector<char>, ::std::array<char, 8>, + ::std::vector<unsigned char>, ::std::vector<signed char>, ::std::string, + ::std::basic_string<signed char>, ::std::basic_string<unsigned char>, + ::std::vector<unsigned char, NonstandardAllocator<unsigned char>>, + ::std::basic_string<char, ::std::char_traits<char>, + NonstandardAllocator<char>>> + ReadOnlyContiguousContainerTypes; +TYPED_TEST_SUITE(ReadOnlyContiguousBufferTest, + ReadOnlyContiguousContainerTypes); + +TYPED_TEST(ReadOnlyContiguousBufferTest, ConstructionFromContainers) { + const TypeParam bytes = {{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}}; + using CharType = + typename ::std::remove_reference<decltype(*bytes.data())>::type; + const auto buffer = ContiguousBuffer<const CharType, 1, 0>{&bytes}; + EXPECT_EQ(bytes.size(), buffer.SizeInBytes()); + EXPECT_TRUE(buffer.Ok()); + EXPECT_EQ(0x0807060504030201UL, buffer.template ReadBigEndianUInt<64>()); + + const auto offset_buffer = buffer.template GetOffsetStorage<1, 0>(4, 4); + EXPECT_EQ(4, offset_buffer.SizeInBytes()); + EXPECT_EQ(0x04030201U, offset_buffer.template ReadBigEndianUInt<32>()); + + // The size of the resulting buffer should be the minimum of the available + // size and the requested size. + EXPECT_EQ(bytes.size() - 4, + (buffer.template GetOffsetStorage<1, 0>(2, bytes.size() - 4) + .SizeInBytes())); + EXPECT_EQ( + 0, + (buffer.template GetOffsetStorage<1, 0>(bytes.size(), 4).SizeInBytes())); +} + +// ContiguousBuffer tests for std::vector and std::array types. +template <typename T> +class ReadWriteContiguousBufferTest : public ::testing::Test {}; +typedef ::testing::Types</**/ ::std::vector<char>, ::std::array<char, 8>, + ::std::vector<unsigned char>, + ::std::vector<signed char>> + ReadWriteContiguousContainerTypes; +TYPED_TEST_SUITE(ReadWriteContiguousBufferTest, + ReadWriteContiguousContainerTypes); + +TYPED_TEST(ReadWriteContiguousBufferTest, ConstructionFromContainers) { + TypeParam bytes = {{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}}; + using CharType = + typename ::std::remove_reference<decltype(*bytes.data())>::type; + const auto buffer = ContiguousBuffer<CharType, 1, 0>{&bytes}; + + // Read and Ok methods should work just as in ReadOnlyContiguousBuffer. + EXPECT_EQ(bytes.size(), buffer.SizeInBytes()); + EXPECT_TRUE(buffer.Ok()); + EXPECT_EQ(0x0807060504030201UL, buffer.template ReadBigEndianUInt<64>()); + + buffer.template WriteBigEndianUInt<64>(0x0102030405060708UL); + EXPECT_EQ((TypeParam{{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}), + bytes); + + bytes[4] = static_cast<CharType>(255); + EXPECT_EQ(0x1020304ff060708, buffer.template ReadBigEndianUInt<64>()); +} + +TEST(ContiguousBuffer, ReturnTypeOfReadUInt) { + const auto buffer = ContiguousBuffer<char, 1, 0>(); + + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<64>()), + uint64_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<48>()), + uint64_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<32>()), + uint32_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<16>()), + uint16_t>::value)); + EXPECT_TRUE(( + ::std::is_same<decltype(buffer.ReadBigEndianUInt<8>()), uint8_t>::value)); + + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<64>()), + uint64_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<48>()), + uint64_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<32>()), + uint32_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<16>()), + uint16_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<8>()), + uint8_t>::value)); + + EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<64>()), + uint64_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<48>()), + uint64_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<32>()), + uint32_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<16>()), + uint16_t>::value)); + EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<8>()), + uint8_t>::value)); + + EXPECT_TRUE( + (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<64>()), + uint64_t>::value)); + EXPECT_TRUE( + (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<48>()), + uint64_t>::value)); + EXPECT_TRUE( + (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<32>()), + uint32_t>::value)); + EXPECT_TRUE( + (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<16>()), + uint16_t>::value)); + EXPECT_TRUE( + (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<8>()), + uint8_t>::value)); +} + +TEST(ReadOnlyContiguousBuffer, Methods) { + const ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, + 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, + 0x04, 0x03, 0x02, 0x01}}; + const auto buffer = ReadOnlyContiguousBuffer{bytes.data(), bytes.size() - 4}; + EXPECT_DEATH(buffer.ReadBigEndianUInt<64>(), ""); + EXPECT_TRUE(buffer.Ok()); + EXPECT_EQ(bytes.size() - 4, buffer.SizeInBytes()); + EXPECT_EQ(0x100f0e0d0c0b0a09, buffer.UncheckedReadBigEndianUInt<64>()); + EXPECT_EQ(0x090a0b0c0d0e0f10, buffer.UncheckedReadLittleEndianUInt<64>()); + + const auto offset_buffer = buffer.GetOffsetStorage<1, 0>(4, 4); + EXPECT_EQ(0x0c0b0a09, offset_buffer.ReadBigEndianUInt<32>()); + EXPECT_EQ(0x090a0b0c, offset_buffer.ReadLittleEndianUInt<32>()); + EXPECT_EQ(0x0c0b0a0908070605, offset_buffer.UncheckedReadBigEndianUInt<64>()); + EXPECT_EQ(4, offset_buffer.SizeInBytes()); + EXPECT_TRUE(offset_buffer.Ok()); + + const auto small_offset_buffer = buffer.GetOffsetStorage<1, 0>(4, 1); + EXPECT_EQ(0x0c, small_offset_buffer.ReadBigEndianUInt<8>()); + EXPECT_EQ(0x0c, small_offset_buffer.ReadLittleEndianUInt<8>()); + EXPECT_EQ(1, small_offset_buffer.SizeInBytes()); + EXPECT_TRUE(small_offset_buffer.Ok()); + + EXPECT_FALSE(ReadOnlyContiguousBuffer().Ok()); + EXPECT_FALSE( + (ReadOnlyContiguousBuffer{static_cast<char *>(nullptr), 12}.Ok())); + EXPECT_DEATH((ReadOnlyContiguousBuffer{static_cast<char *>(nullptr), 4} + .ReadBigEndianUInt<32>()), + ""); + EXPECT_EQ(0, ReadOnlyContiguousBuffer().SizeInBytes()); + EXPECT_EQ(0, (ReadOnlyContiguousBuffer{static_cast<char *>(nullptr), 12} + .SizeInBytes())); + EXPECT_DEATH( + (ReadOnlyContiguousBuffer{bytes.data(), 0}.ReadBigEndianUInt<8>()), ""); + + // The size of the resulting buffer should be the minimum of the available + // size and the requested size. + EXPECT_EQ(bytes.size() - 8, + (buffer.GetOffsetStorage<1, 0>(4, bytes.size() - 4).SizeInBytes())); + EXPECT_EQ(4, (buffer.GetOffsetStorage<1, 0>(0, 4).SizeInBytes())); + EXPECT_EQ(0, (buffer.GetOffsetStorage<1, 0>(bytes.size(), 4).SizeInBytes())); + EXPECT_FALSE((ReadOnlyContiguousBuffer().GetOffsetStorage<1, 0>(0, 0).Ok())); +} + +TEST(ReadWriteContiguousBuffer, Methods) { + ::std::vector<uint8_t> bytes = { + {0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}}; + const auto buffer = ReadWriteContiguousBuffer{bytes.data(), bytes.size() - 4}; + // Read and Ok methods should work just as in ReadOnlyContiguousBuffer. + EXPECT_TRUE(buffer.Ok()); + EXPECT_EQ(bytes.size() - 4, buffer.SizeInBytes()); + EXPECT_EQ(0x0c0b0a0908070605, buffer.ReadBigEndianUInt<64>()); + + buffer.WriteBigEndianUInt<64>(0x05060708090a0b0c); + EXPECT_EQ((::std::vector<uint8_t>{0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x04, 0x03, 0x02, 0x01}), + bytes); + buffer.WriteLittleEndianUInt<64>(0x05060708090a0b0c); + EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01}), + bytes); + + const auto offset_buffer = buffer.GetOffsetStorage<1, 0>(4, 4); + offset_buffer.WriteBigEndianUInt<32>(0x05060708); + EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x05, 0x06, 0x07, + 0x08, 0x04, 0x03, 0x02, 0x01}), + bytes); + offset_buffer.WriteLittleEndianUInt<32>(0x05060708); + EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01}), + bytes); + + const auto small_offset_buffer = buffer.GetOffsetStorage<1, 0>(4, 1); + small_offset_buffer.WriteBigEndianUInt<8>(0x80); + EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x80, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01}), + bytes); + small_offset_buffer.WriteLittleEndianUInt<8>(0x08); + EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01}), + bytes); + + EXPECT_DEATH(ReadWriteContiguousBuffer().ReadLittleEndianUInt<8>(), ""); + EXPECT_DEATH( + (ReadWriteContiguousBuffer{static_cast<unsigned char *>(nullptr), 1} + .ReadLittleEndianUInt<8>()), + ""); + EXPECT_DEATH( + (ReadWriteContiguousBuffer{static_cast<unsigned char *>(nullptr), 1} + .WriteLittleEndianUInt<8>(0xff)), + ""); +} + +TEST(ContiguousBuffer, AssignmentFromCompatibleContiguousBuffers) { + alignas(4) char data[8]; + ContiguousBuffer<const unsigned char, 1, 0> buffer; + buffer = ContiguousBuffer<char, 4, 1>(data + 1, sizeof data - 1); + EXPECT_TRUE(buffer.Ok()); + EXPECT_EQ(buffer.data(), reinterpret_cast<unsigned char *>(data + 1)); + + ContiguousBuffer<const signed char, 2, 1> aligned_buffer; + aligned_buffer = + ContiguousBuffer<unsigned char, 4, 3>(data + 3, sizeof data - 3); + EXPECT_TRUE(aligned_buffer.Ok()); + EXPECT_EQ(aligned_buffer.data(), reinterpret_cast<signed char *>(data + 3)); +} + +TEST(ContiguousBuffer, ConstructionFromCompatibleContiguousBuffers) { + alignas(4) char data[8]; + ContiguousBuffer<const unsigned char, 1, 0> buffer{ + ContiguousBuffer<char, 4, 1>(data + 1, sizeof data - 1)}; + EXPECT_TRUE(buffer.Ok()); + EXPECT_EQ(buffer.data(), reinterpret_cast<unsigned char *>(data + 1)); + + ContiguousBuffer<const signed char, 2, 1> aligned_buffer{ + ContiguousBuffer<unsigned char, 4, 3>(data + 3, sizeof data - 3)}; + EXPECT_TRUE(aligned_buffer.Ok()); + EXPECT_EQ(aligned_buffer.data(), reinterpret_cast<signed char *>(data + 3)); +} + +TEST(LittleEndianByteOrderer, Methods) { + ::std::vector<uint8_t> bytes = {{21, 22, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24}}; + const int buffer_start = 2; + const auto buffer = LittleEndianByteOrderer<ReadWriteContiguousBuffer>{ + ReadWriteContiguousBuffer{bytes.data() + buffer_start, 8}}; + EXPECT_EQ(8, buffer.SizeInBytes()); + EXPECT_TRUE(buffer.Ok()); + EXPECT_EQ(0x0807060504030201, buffer.ReadUInt<64>()); + EXPECT_EQ(0x0807060504030201, buffer.UncheckedReadUInt<64>()); + EXPECT_DEATH(buffer.ReadUInt<56>(), ""); + EXPECT_EQ(0x07060504030201, buffer.UncheckedReadUInt<56>()); + buffer.WriteUInt<64>(0x0102030405060708); + EXPECT_EQ((::std::vector<uint8_t>{21, 22, 8, 7, 6, 5, 4, 3, 2, 1, 23, 24}), + bytes); + buffer.UncheckedWriteUInt<64>(0x0807060504030201); + EXPECT_EQ((::std::vector<uint8_t>{21, 22, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24}), + bytes); + EXPECT_DEATH(buffer.WriteUInt<56>(0x77777777777777), ""); + + EXPECT_FALSE(LittleEndianByteOrderer<ReadOnlyContiguousBuffer>().Ok()); + EXPECT_EQ(0, + LittleEndianByteOrderer<ReadOnlyContiguousBuffer>().SizeInBytes()); + EXPECT_EQ(bytes[1], (LittleEndianByteOrderer<ReadOnlyContiguousBuffer>{ + ReadOnlyContiguousBuffer{bytes.data() + 1, 0}} + .UncheckedReadUInt<8>())); + EXPECT_TRUE((LittleEndianByteOrderer<ReadOnlyContiguousBuffer>{ + ReadOnlyContiguousBuffer{bytes.data(), 0}} + .Ok())); +} + +TEST(BigEndianByteOrderer, Methods) { + ::std::vector<uint8_t> bytes = {{21, 22, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24}}; + const int buffer_start = 2; + const auto buffer = BigEndianByteOrderer<ReadWriteContiguousBuffer>{ + ReadWriteContiguousBuffer{bytes.data() + buffer_start, 8}}; + EXPECT_EQ(8, buffer.SizeInBytes()); + EXPECT_TRUE(buffer.Ok()); + EXPECT_EQ(0x0102030405060708, buffer.ReadUInt<64>()); + EXPECT_EQ(0x0102030405060708, buffer.UncheckedReadUInt<64>()); + EXPECT_DEATH(buffer.ReadUInt<56>(), ""); + EXPECT_EQ(0x01020304050607, buffer.UncheckedReadUInt<56>()); + buffer.WriteUInt<64>(0x0807060504030201); + EXPECT_EQ((::std::vector<uint8_t>{21, 22, 8, 7, 6, 5, 4, 3, 2, 1, 23, 24}), + bytes); + buffer.UncheckedWriteUInt<64>(0x0102030405060708); + EXPECT_EQ((::std::vector<uint8_t>{21, 22, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24}), + bytes); + EXPECT_DEATH(buffer.WriteUInt<56>(0x77777777777777), ""); + + EXPECT_FALSE(BigEndianByteOrderer<ReadOnlyContiguousBuffer>().Ok()); + EXPECT_EQ(0, BigEndianByteOrderer<ReadOnlyContiguousBuffer>().SizeInBytes()); + EXPECT_EQ(bytes[1], (BigEndianByteOrderer<ReadOnlyContiguousBuffer>{ + ReadOnlyContiguousBuffer{bytes.data() + 1, 0}} + .UncheckedReadUInt<8>())); + EXPECT_TRUE((BigEndianByteOrderer<ReadOnlyContiguousBuffer>{ + ReadOnlyContiguousBuffer{bytes.data(), 0}} + .Ok())); +} + +TEST(NullByteOrderer, Methods) { + uint8_t bytes[] = {0xdb, 0x0f, 0x0e, 0x0d}; + const auto buffer = NullByteOrderer<ReadWriteContiguousBuffer>{ + ReadWriteContiguousBuffer{bytes, 1}}; + EXPECT_EQ(bytes[0], buffer.ReadUInt<8>()); + EXPECT_EQ(bytes[0], buffer.UncheckedReadUInt<8>()); + // NullByteOrderer::UncheckedRead ignores its argument. + EXPECT_EQ(bytes[0], buffer.UncheckedReadUInt<8>()); + buffer.WriteUInt<8>(0x24); + EXPECT_EQ(0x24, bytes[0]); + buffer.UncheckedWriteUInt<8>(0x25); + EXPECT_EQ(0x25, bytes[0]); + EXPECT_EQ(1, buffer.SizeInBytes()); + EXPECT_TRUE(buffer.Ok()); + + EXPECT_FALSE(NullByteOrderer<ReadOnlyContiguousBuffer>().Ok()); + EXPECT_EQ(0, NullByteOrderer<ReadOnlyContiguousBuffer>().SizeInBytes()); + EXPECT_DEATH((NullByteOrderer<ReadOnlyContiguousBuffer>{ + ReadOnlyContiguousBuffer{bytes, 0}} + .ReadUInt<8>()), + ""); + EXPECT_DEATH((NullByteOrderer<ReadOnlyContiguousBuffer>{ + ReadOnlyContiguousBuffer{bytes, 2}} + .ReadUInt<8>()), + ""); + EXPECT_EQ(bytes[0], (NullByteOrderer<ReadOnlyContiguousBuffer>{ + ReadOnlyContiguousBuffer{bytes, 0}} + .UncheckedReadUInt<8>())); + EXPECT_TRUE((NullByteOrderer<ReadOnlyContiguousBuffer>{ + ReadOnlyContiguousBuffer{bytes, 0}} + .Ok())); +} + +TEST(BitBlock, BigEndianMethods) { + uint8_t bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + const auto big_endian = + BigEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes + 4, 8}}; + EXPECT_EQ(64, big_endian.SizeInBits()); + EXPECT_TRUE(big_endian.Ok()); + EXPECT_EQ(0x05060708090a0b0cUL, big_endian.ReadUInt()); + EXPECT_EQ(0x05060708090a0b0cUL, big_endian.UncheckedReadUInt()); + EXPECT_FALSE(BigEndianBitBlockN<64>().Ok()); + EXPECT_EQ(64, BigEndianBitBlockN<64>().SizeInBits()); + EXPECT_FALSE( + (BigEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes, 0}}.Ok())); +} + +TEST(BitBlock, LittleEndianMethods) { + uint8_t bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + const auto little_endian = + LittleEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes + 4, 8}}; + EXPECT_EQ(64, little_endian.SizeInBits()); + EXPECT_TRUE(little_endian.Ok()); + EXPECT_EQ(0x0c0b0a0908070605UL, little_endian.ReadUInt()); + EXPECT_EQ(0x0c0b0a0908070605UL, little_endian.UncheckedReadUInt()); + EXPECT_FALSE(LittleEndianBitBlockN<64>().Ok()); + EXPECT_EQ(64, LittleEndianBitBlockN<64>().SizeInBits()); + EXPECT_FALSE( + (LittleEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes, 0}}.Ok())); +} + +TEST(BitBlock, GetOffsetStorage) { + uint8_t bytes[] = {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}; + const auto bit_block = + LittleEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes, 8}}; + const OffsetBitBlock<LittleEndianBitBlockN<64>> offset_block = + bit_block.GetOffsetStorage<1, 0>(4, 8); + EXPECT_EQ(8, offset_block.SizeInBits()); + EXPECT_EQ(0xf1, offset_block.ReadUInt()); + EXPECT_EQ(bit_block.SizeInBits(), + (bit_block.GetOffsetStorage<1, 0>(8, bit_block.SizeInBits()) + .SizeInBits())); + EXPECT_FALSE( + (bit_block.GetOffsetStorage<1, 0>(8, bit_block.SizeInBits()).Ok())); + EXPECT_EQ(10, (bit_block.GetOffsetStorage<1, 0>(bit_block.SizeInBits(), 10) + .SizeInBits())); +} + +TEST(OffsetBitBlock, Methods) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09}}; + const auto bit_block = + LittleEndianBitBlockN<64>{ReadWriteContiguousBuffer{&bytes}}; + EXPECT_FALSE((bit_block.GetOffsetStorage<1, 0>(0, 96).Ok())); + EXPECT_TRUE((bit_block.GetOffsetStorage<1, 0>(0, 64).Ok())); + + const auto offset_block = bit_block.GetOffsetStorage<1, 0>(8, 48); + EXPECT_FALSE((offset_block.GetOffsetStorage<1, 0>(40, 16).Ok())); + EXPECT_EQ(0x0a0b0c0d0e0f, offset_block.ReadUInt()); + EXPECT_EQ(0x0a0b0c0d0e0f, offset_block.UncheckedReadUInt()); + offset_block.WriteUInt(0x0f0e0d0c0b0a); + EXPECT_EQ( + (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x09}), + bytes); + offset_block.UncheckedWriteUInt(0x0a0b0c0d0e0f); + EXPECT_EQ( + (::std::vector<uint8_t>{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09}), + bytes); + EXPECT_DEATH(offset_block.WriteUInt(0x10f0e0d0c0b0a), ""); + offset_block.UncheckedWriteUInt(0x10f0e0d0c0b0a); + EXPECT_EQ( + (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x09}), + bytes); + + const auto offset_offset_block = offset_block.GetOffsetStorage<1, 0>(16, 16); + EXPECT_FALSE((offset_offset_block.GetOffsetStorage<1, 0>(8, 16).Ok())); + EXPECT_EQ(0x0d0c, offset_offset_block.ReadUInt()); + EXPECT_EQ(0x0d0c, offset_offset_block.UncheckedReadUInt()); + offset_offset_block.WriteUInt(0x0c0d); + EXPECT_EQ( + (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0d, 0x0c, 0x0e, 0x0f, 0x09}), + bytes); + offset_offset_block.UncheckedWriteUInt(0x0d0c); + EXPECT_EQ( + (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x09}), + bytes); + EXPECT_DEATH(offset_offset_block.WriteUInt(0x10c0d), ""); + offset_offset_block.UncheckedWriteUInt(0x20c0d); + EXPECT_EQ( + (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0d, 0x0c, 0x0e, 0x0f, 0x09}), + bytes); + + const auto null_offset_block = OffsetBitBlock<BigEndianBitBlockN<32>>(); + EXPECT_FALSE(null_offset_block.Ok()); + EXPECT_EQ(0, null_offset_block.SizeInBits()); +} + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_prelude.h b/public/emboss_prelude.h new file mode 100644 index 0000000..85eeba8 --- /dev/null +++ b/public/emboss_prelude.h
@@ -0,0 +1,803 @@ +// 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_PUBLIC_EMBOSS_PRELUDE_H_ +#define EMBOSS_PUBLIC_EMBOSS_PRELUDE_H_ + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <limits> +#include <type_traits> +#include <utility> + +#include "public/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 { EMBOSS_CHECK(TryToWrite(value)); } + 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 { + EMBOSS_CHECK(TryToWrite(value)); + } + + 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 { + EMBOSS_CHECK(TryToWrite(value)); + } + + 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); + } + } 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 { EMBOSS_CHECK(TryToWrite(value)); } + 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 { EMBOSS_CHECK(TryToWrite(value)); } + 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_PUBLIC_EMBOSS_PRELUDE_H_
diff --git a/public/emboss_prelude_test.cc b/public/emboss_prelude_test.cc new file mode 100644 index 0000000..97658b1 --- /dev/null +++ b/public/emboss_prelude_test.cc
@@ -0,0 +1,727 @@ +// 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. + +#include "public/emboss_prelude.h" + +#include <type_traits> + +#include "public/emboss_cpp_util.h" +#include <gtest/gtest.h> + +namespace emboss { +namespace prelude { +namespace test { + +using ::emboss::support::OffsetBitBlock; +using ::emboss::support::ReadWriteContiguousBuffer; + +template <size_t kBits> +using BitBlockN = ::emboss::support::BitBlock< + ::emboss::support::LittleEndianByteOrderer<ReadWriteContiguousBuffer>, + kBits>; + +template <size_t kBits> +using ViewParameters = ::emboss::support::FixedSizeViewParameters< + kBits, ::emboss::support::AllValuesAreOk>; + +TEST(FlagView, Methods) { + uint8_t byte = 0; + auto flag_view = + FlagView<ViewParameters<1>, OffsetBitBlock<BitBlockN<8>>>{BitBlockN<8>{ + ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)}; + EXPECT_FALSE(flag_view.Read()); + byte = 0xfe; + EXPECT_FALSE(flag_view.Read()); + byte = 0x01; + EXPECT_TRUE(flag_view.Read()); + byte = 0xff; + EXPECT_TRUE(flag_view.Read()); + EXPECT_TRUE(flag_view.CouldWriteValue(false)); + EXPECT_TRUE(flag_view.CouldWriteValue(true)); + flag_view.Write(false); + EXPECT_EQ(0xfe, byte); + byte = 0xaa; + flag_view.Write(true); + EXPECT_EQ(0xab, byte); +} + +TEST(FlagView, TextDecode) { + uint8_t byte = 0; + const auto flag_view = + FlagView<ViewParameters<1>, OffsetBitBlock<BitBlockN<8>>>{BitBlockN<8>{ + ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)}; + EXPECT_FALSE(UpdateFromText(flag_view, "")); + EXPECT_FALSE(UpdateFromText(flag_view, "FALSE")); + EXPECT_FALSE(UpdateFromText(flag_view, "TRUE")); + EXPECT_FALSE(UpdateFromText(flag_view, "+true")); + EXPECT_TRUE(UpdateFromText(flag_view, "true")); + EXPECT_EQ(0x01, byte); + EXPECT_TRUE(UpdateFromText(flag_view, "false")); + EXPECT_EQ(0x00, byte); + EXPECT_TRUE(UpdateFromText(flag_view, " true")); + EXPECT_EQ(0x01, byte); + { + auto stream = support::TextStream{" false xxx"}; + EXPECT_TRUE(flag_view.UpdateFromTextStream(&stream)); + EXPECT_EQ(0x00, byte); + ::std::string token; + EXPECT_TRUE(::emboss::support::ReadToken(&stream, &token)); + EXPECT_EQ("xxx", token); + } +} + +TEST(FlagView, TextEncode) { + uint8_t byte = 0; + const auto flag_view = + FlagView<ViewParameters<1>, OffsetBitBlock<BitBlockN<8>>>{BitBlockN<8>{ + ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)}; + EXPECT_EQ("false", WriteToString(flag_view)); + byte = 1; + EXPECT_EQ("true", WriteToString(flag_view)); +} + +template <template <typename, typename> class ViewType, int kMaxBits> +void CheckViewSizeInBits() { + const int size_in_bits = + ViewType<ViewParameters<kMaxBits>, BitBlockN<64>>::SizeInBits(); + EXPECT_EQ(size_in_bits, kMaxBits); + return CheckViewSizeInBits<ViewType, kMaxBits - 1>(); +} + +template <> +void CheckViewSizeInBits<UIntView, 0>() { + return; +} + +template <> +void CheckViewSizeInBits<IntView, 0>() { + return; +} + +template <> +void CheckViewSizeInBits<BcdView, 0>() { + return; +} + +TEST(UIntView, SizeInBits) { CheckViewSizeInBits<UIntView, 64>(); } + +TEST(IntView, SizeInBits) { CheckViewSizeInBits<IntView, 64>(); } + +TEST(BcdView, SizeInBits) { CheckViewSizeInBits<BcdView, 64>(); } + +template <size_t kBits> +using UIntViewN = UIntView<ViewParameters<kBits>, BitBlockN<kBits>>; + +TEST(UIntView, ValueType) { + using BitBlockType = BitBlockN<64>; + EXPECT_TRUE( + (::std::is_same<uint8_t, UIntView<ViewParameters<8>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint8_t, UIntView<ViewParameters<6>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint16_t, UIntView<ViewParameters<9>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint16_t, UIntView<ViewParameters<16>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint32_t, UIntView<ViewParameters<17>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint32_t, UIntView<ViewParameters<32>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint64_t, UIntView<ViewParameters<33>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint64_t, UIntView<ViewParameters<64>, + BitBlockType>::ValueType>::value)); +} + +TEST(UIntView, CouldWriteValue) { + EXPECT_TRUE(UIntViewN<8>::CouldWriteValue(0xff)); + EXPECT_TRUE(UIntViewN<8>::CouldWriteValue(0)); + EXPECT_FALSE(UIntViewN<8>::CouldWriteValue(0x100)); + EXPECT_FALSE(UIntViewN<8>::CouldWriteValue(-1)); + EXPECT_TRUE(UIntViewN<16>::CouldWriteValue(0xffff)); + EXPECT_TRUE(UIntViewN<16>::CouldWriteValue(0)); + EXPECT_FALSE(UIntViewN<16>::CouldWriteValue(0x10000)); + EXPECT_FALSE(UIntViewN<16>::CouldWriteValue(-1)); + EXPECT_TRUE(UIntViewN<32>::CouldWriteValue(0xffffffffU)); + EXPECT_TRUE(UIntViewN<32>::CouldWriteValue(0xffffffffL)); + EXPECT_TRUE(UIntViewN<32>::CouldWriteValue(0)); + EXPECT_FALSE(UIntViewN<32>::CouldWriteValue(0x100000000L)); + EXPECT_FALSE(UIntViewN<32>::CouldWriteValue(-1)); + EXPECT_TRUE(UIntViewN<48>::CouldWriteValue(0x0000ffffffffffffUL)); + EXPECT_TRUE(UIntViewN<48>::CouldWriteValue(0x0000ffffffffffffL)); + EXPECT_TRUE(UIntViewN<48>::CouldWriteValue(0)); + EXPECT_FALSE(UIntViewN<48>::CouldWriteValue(0x1000000000000UL)); + EXPECT_FALSE(UIntViewN<48>::CouldWriteValue(0x1000000000000L)); + EXPECT_FALSE(UIntViewN<48>::CouldWriteValue(-1)); + EXPECT_TRUE(UIntViewN<64>::CouldWriteValue(0xffffffffffffffffUL)); + EXPECT_TRUE(UIntViewN<64>::CouldWriteValue(0)); + EXPECT_FALSE(UIntViewN<64>::CouldWriteValue(-1)); +} + +TEST(UIntView, CouldWriteValueNarrowing) { + auto narrowing_could_write = [](int value) { + return UIntViewN<8>::CouldWriteValue(value); + }; + EXPECT_TRUE(narrowing_could_write(0)); + EXPECT_TRUE(narrowing_could_write(255)); + EXPECT_FALSE(narrowing_could_write(-1)); + EXPECT_FALSE(narrowing_could_write(256)); +} + +TEST(UIntView, ReadAndWriteWithSufficientBuffer) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}; + auto uint64_view = + UIntViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}}; + EXPECT_EQ(0x090a0b0c0d0e0f10UL, uint64_view.Read()); + EXPECT_EQ(0x090a0b0c0d0e0f10UL, uint64_view.UncheckedRead()); + uint64_view.Write(0x100f0e0d0c0b0a09UL); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}), + bytes); + uint64_view.UncheckedWrite(0x090a0b0c0d0e0f10UL); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}), + bytes); + EXPECT_TRUE(uint64_view.TryToWrite(0x100f0e0d0c0b0a09UL)); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}), + bytes); + EXPECT_TRUE(uint64_view.TryToWrite(0x090a0b0c0d0e0f10UL)); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}), + bytes); + EXPECT_TRUE(uint64_view.Ok()); + EXPECT_TRUE(uint64_view.IsComplete()); +} + +TEST(UIntView, ReadAndWriteWithInsufficientBuffer) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}; + auto uint64_view = + UIntViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 4}}}; + EXPECT_DEATH(uint64_view.Read(), ""); + EXPECT_EQ(0x090a0b0c0d0e0f10UL, uint64_view.UncheckedRead()); + EXPECT_DEATH(uint64_view.Write(0x100f0e0d0c0b0a09UL), ""); + EXPECT_FALSE(uint64_view.TryToWrite(0x100f0e0d0c0b0a09UL)); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}), + bytes); + uint64_view.UncheckedWrite(0x100f0e0d0c0b0a09UL); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}), + bytes); + EXPECT_FALSE(uint64_view.Ok()); + EXPECT_FALSE(uint64_view.IsComplete()); + uint64_view.UncheckedWrite(0x090a0b0c0d0e0f10UL); +} + +TEST(UIntView, NonPowerOfTwoSize) { + ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}}; + auto uint24_view = + UIntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}}; + EXPECT_EQ(0x0e0f10, uint24_view.Read()); + EXPECT_EQ(0x0e0f10, uint24_view.UncheckedRead()); + EXPECT_DEATH(uint24_view.Write(0x1000000), ""); + uint24_view.Write(0x100f0e); + EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0x10, 0x0d}}), bytes); + uint24_view.UncheckedWrite(0x1000000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x0d}}), bytes); + EXPECT_TRUE(uint24_view.Ok()); + EXPECT_TRUE(uint24_view.IsComplete()); +} + +TEST(UIntView, NonPowerOfTwoSizeInsufficientBuffer) { + ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}}; + auto uint24_view = + UIntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 2}}}; + EXPECT_DEATH(uint24_view.Read(), ""); + EXPECT_EQ(0x0e0f10, uint24_view.UncheckedRead()); + EXPECT_DEATH(uint24_view.Write(0x100f0e), ""); + uint24_view.UncheckedWrite(0x100f0e); + EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0x10, 0x0d}}), bytes); + uint24_view.UncheckedWrite(0x1000000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x0d}}), bytes); + EXPECT_FALSE(uint24_view.Ok()); + EXPECT_FALSE(uint24_view.IsComplete()); +} + +TEST(UIntView, NonByteSize) { + ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x80, 0x80}}; + auto uint23_view = + UIntView<ViewParameters<23>, OffsetBitBlock<BitBlockN<24>>>{BitBlockN<24>{ + ReadWriteContiguousBuffer{bytes.data(), + 3}}.GetOffsetStorage<1, 0>(0, 23)}; + EXPECT_EQ(0x0, uint23_view.Read()); + EXPECT_FALSE(uint23_view.CouldWriteValue(0x800f0e)); + EXPECT_FALSE(uint23_view.CouldWriteValue(0x800000)); + EXPECT_TRUE(uint23_view.CouldWriteValue(0x7fffff)); + EXPECT_DEATH(uint23_view.Write(0x800f0e), ""); + uint23_view.Write(0x400f0e); + EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0xc0, 0x80}}), bytes); + uint23_view.UncheckedWrite(0x1000000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x80, 0x80}}), bytes); + EXPECT_TRUE(uint23_view.Ok()); + EXPECT_TRUE(uint23_view.IsComplete()); +} + +TEST(UIntView, TextDecode) { + ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x00, 0xff}}; + const auto uint24_view = + UIntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}}; + EXPECT_TRUE(UpdateFromText(uint24_view, "23")); + EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes); + EXPECT_EQ(23, uint24_view.Read()); + EXPECT_FALSE(UpdateFromText(uint24_view, "16777216")); + EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes); + EXPECT_TRUE(UpdateFromText(uint24_view, "16777215")); + EXPECT_EQ((::std::vector<uint8_t>{{0xff, 0xff, 0xff, 0xff}}), bytes); + EXPECT_TRUE(UpdateFromText(uint24_view, "0x01_0203")); + EXPECT_EQ((::std::vector<uint8_t>{{0x03, 0x02, 0x01, 0xff}}), bytes); +} + +template <size_t kBits> +using IntViewN = IntView<ViewParameters<kBits>, BitBlockN<kBits>>; + +TEST(IntView, ValueType) { + using BitBlockType = BitBlockN<64>; + EXPECT_TRUE( + (::std::is_same< + int8_t, IntView<ViewParameters<8>, BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same< + int8_t, IntView<ViewParameters<6>, BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<int16_t, IntView<ViewParameters<9>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<int16_t, IntView<ViewParameters<16>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<int32_t, IntView<ViewParameters<17>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<int32_t, IntView<ViewParameters<32>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<int64_t, IntView<ViewParameters<33>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<int64_t, IntView<ViewParameters<64>, + BitBlockType>::ValueType>::value)); +} + +TEST(IntView, CouldWriteValue) { + // Note that many values are in decimal in order to avoid C++'s implicit + // conversions to unsigned for hex constants. + EXPECT_TRUE(IntViewN<8>::CouldWriteValue(0x7f)); + EXPECT_TRUE(IntViewN<8>::CouldWriteValue(-0x80)); + EXPECT_FALSE(IntViewN<8>::CouldWriteValue(0x80)); + EXPECT_FALSE(IntViewN<8>::CouldWriteValue(0x8000000000000000UL)); + EXPECT_FALSE(IntViewN<8>::CouldWriteValue(-0x81)); + EXPECT_TRUE(IntViewN<16>::CouldWriteValue(32767)); + EXPECT_TRUE(IntViewN<16>::CouldWriteValue(0)); + EXPECT_FALSE(IntViewN<16>::CouldWriteValue(0x8000)); + EXPECT_FALSE(IntViewN<16>::CouldWriteValue(-0x8001)); + EXPECT_TRUE(IntViewN<32>::CouldWriteValue(0x7fffffffU)); + EXPECT_TRUE(IntViewN<32>::CouldWriteValue(0x7fffffffL)); + EXPECT_FALSE(IntViewN<32>::CouldWriteValue(0x80000000U)); + EXPECT_FALSE(IntViewN<32>::CouldWriteValue(-2147483649L)); + EXPECT_TRUE(IntViewN<48>::CouldWriteValue(0x00007fffffffffffUL)); + EXPECT_FALSE(IntViewN<48>::CouldWriteValue(140737488355328L)); + EXPECT_FALSE(IntViewN<48>::CouldWriteValue(-140737488355329L)); + EXPECT_TRUE(IntViewN<64>::CouldWriteValue(0x7fffffffffffffffUL)); + EXPECT_TRUE(IntViewN<64>::CouldWriteValue(9223372036854775807L)); + EXPECT_TRUE(IntViewN<64>::CouldWriteValue(-9223372036854775807L - 1)); + EXPECT_FALSE(IntViewN<64>::CouldWriteValue(0x8000000000000000UL)); +} + +TEST(IntView, CouldWriteValueNarrowing) { + auto narrowing_could_write = [](int value) { + return IntViewN<8>::CouldWriteValue(value); + }; + EXPECT_TRUE(narrowing_could_write(-128)); + EXPECT_TRUE(narrowing_could_write(127)); + EXPECT_FALSE(narrowing_could_write(-129)); + EXPECT_FALSE(narrowing_could_write(128)); +} + +TEST(IntView, ReadAndWriteWithSufficientBuffer) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}; + auto int64_view = + IntViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}}; + EXPECT_EQ(0x090a0b0c0d0e0f10L, int64_view.Read()); + EXPECT_EQ(0x090a0b0c0d0e0f10L, int64_view.UncheckedRead()); + int64_view.Write(0x100f0e0d0c0b0a09L); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}), + bytes); + int64_view.UncheckedWrite(0x090a0b0c0d0e0f10L); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}), + bytes); + EXPECT_TRUE(int64_view.TryToWrite(0x100f0e0d0c0b0a09L)); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}), + bytes); + int64_view.Write(-0x100f0e0d0c0b0a09L); + EXPECT_EQ(-0x100f0e0d0c0b0a09L, int64_view.Read()); + EXPECT_EQ((::std::vector<uint8_t>{ + {0xf7, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, 0xef, 0x08}}), + bytes); + EXPECT_TRUE(int64_view.Ok()); + EXPECT_TRUE(int64_view.IsComplete()); +} + +TEST(IntView, ReadAndWriteWithInsufficientBuffer) { + ::std::vector<uint8_t> bytes = { + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}; + auto int64_view = + IntViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 4}}}; + EXPECT_DEATH(int64_view.Read(), ""); + EXPECT_EQ(0x090a0b0c0d0e0f10L, int64_view.UncheckedRead()); + EXPECT_DEATH(int64_view.Write(0x100f0e0d0c0b0a09L), ""); + EXPECT_FALSE(int64_view.TryToWrite(0x100f0e0d0c0b0a09L)); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}), + bytes); + int64_view.UncheckedWrite(0x100f0e0d0c0b0a09L); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}), + bytes); + EXPECT_FALSE(int64_view.Ok()); + EXPECT_FALSE(int64_view.IsComplete()); +} + +TEST(IntView, NonPowerOfTwoSize) { + ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}}; + auto int24_view = + IntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}}; + EXPECT_EQ(0x0e0f10, int24_view.Read()); + EXPECT_EQ(0x0e0f10, int24_view.UncheckedRead()); + EXPECT_DEATH(int24_view.Write(0x1000000), ""); + int24_view.Write(0x100f0e); + EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0x10, 0x0d}}), bytes); + int24_view.Write(-0x100f0e); + EXPECT_EQ((::std::vector<uint8_t>{{0xf2, 0xf0, 0xef, 0x0d}}), bytes); + EXPECT_DEATH(int24_view.Write(0x1000000), ""); + int24_view.UncheckedWrite(0x1000000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x0d}}), bytes); + EXPECT_TRUE(int24_view.Ok()); + EXPECT_TRUE(int24_view.IsComplete()); +} + +TEST(IntView, NonPowerOfTwoSizeInsufficientBuffer) { + ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}}; + auto int24_view = + IntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 2}}}; + EXPECT_DEATH(int24_view.Read(), ""); + EXPECT_EQ(0x0e0f10, int24_view.UncheckedRead()); + EXPECT_DEATH(int24_view.Write(0x100f0e), ""); + int24_view.UncheckedWrite(0x100f0e); + EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0x10, 0x0d}}), bytes); + int24_view.UncheckedWrite(0x1000000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x0d}}), bytes); + EXPECT_FALSE(int24_view.Ok()); + EXPECT_FALSE(int24_view.IsComplete()); +} + +TEST(IntView, NonByteSize) { + ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x80, 0x80}}; + auto int23_view = + IntView<ViewParameters<23>, OffsetBitBlock<BitBlockN<24>>>{BitBlockN<24>{ + ReadWriteContiguousBuffer{bytes.data(), + 3}}.GetOffsetStorage<1, 0>(0, 23)}; + EXPECT_EQ(0x0, int23_view.Read()); + EXPECT_FALSE(int23_view.CouldWriteValue(0x400f0e)); + EXPECT_DEATH(int23_view.Write(0x400f0e), ""); + int23_view.Write(0x200f0e); + EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0xa0, 0x80}}), bytes); + int23_view.Write(-0x400000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0xc0, 0x80}}), bytes); + int23_view.UncheckedWrite(0x1000000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x80, 0x80}}), bytes); + EXPECT_TRUE(int23_view.Ok()); + EXPECT_TRUE(int23_view.IsComplete()); +} + +TEST(IntView, OneBit) { + uint8_t bytes[] = {0xfe}; + auto int1_view = + IntView<ViewParameters<1>, OffsetBitBlock<BitBlockN<8>>>{BitBlockN<8>{ + ReadWriteContiguousBuffer{bytes, 1}}.GetOffsetStorage<1, 0>(0, 1)}; + EXPECT_TRUE(int1_view.Ok()); + EXPECT_TRUE(int1_view.IsComplete()); + EXPECT_EQ(0, int1_view.Read()); + EXPECT_FALSE(int1_view.CouldWriteValue(1)); + EXPECT_TRUE(int1_view.CouldWriteValue(0)); + EXPECT_TRUE(int1_view.CouldWriteValue(-1)); + EXPECT_DEATH(int1_view.Write(1), ""); + int1_view.Write(-1); + EXPECT_EQ(0xff, bytes[0]); + EXPECT_EQ(-1, int1_view.Read()); + int1_view.Write(0); + EXPECT_EQ(0xfe, bytes[0]); + bytes[0] = 0; + int1_view.Write(-1); + EXPECT_EQ(0x01, bytes[0]); +} + +TEST(IntView, TextDecode) { + ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x00, 0xff}}; + const auto int24_view = + IntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}}; + EXPECT_TRUE(UpdateFromText(int24_view, "23")); + EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes); + EXPECT_EQ(23, int24_view.Read()); + EXPECT_FALSE(UpdateFromText(int24_view, "16777216")); + EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes); + EXPECT_FALSE(UpdateFromText(int24_view, "16777215")); + EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes); + EXPECT_FALSE(UpdateFromText(int24_view, "8388608")); + EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes); + EXPECT_TRUE(UpdateFromText(int24_view, "8388607")); + EXPECT_EQ((::std::vector<uint8_t>{{0xff, 0xff, 0x7f, 0xff}}), bytes); + EXPECT_TRUE(UpdateFromText(int24_view, "-8388608")); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x80, 0xff}}), bytes); + EXPECT_TRUE(UpdateFromText(int24_view, "-1")); + EXPECT_EQ((::std::vector<uint8_t>{{0xff, 0xff, 0xff, 0xff}}), bytes); + EXPECT_TRUE(UpdateFromText(int24_view, "0x01_0203")); + EXPECT_EQ((::std::vector<uint8_t>{{0x03, 0x02, 0x01, 0xff}}), bytes); + EXPECT_TRUE(UpdateFromText(int24_view, "-0x01_0203")); + EXPECT_EQ((::std::vector<uint8_t>{{0xfd, 0xfd, 0xfe, 0xff}}), bytes); + EXPECT_FALSE(UpdateFromText(int24_view, "- 0x01_0203")); + EXPECT_EQ((::std::vector<uint8_t>{{0xfd, 0xfd, 0xfe, 0xff}}), bytes); +} + +TEST(MaxBcd, Values) { + EXPECT_EQ(0, MaxBcd<uint64_t>(0)); + EXPECT_EQ(1, MaxBcd<uint64_t>(1)); + EXPECT_EQ(3, MaxBcd<uint64_t>(2)); + EXPECT_EQ(7, MaxBcd<uint64_t>(3)); + EXPECT_EQ(9, MaxBcd<uint64_t>(4)); + EXPECT_EQ(19, MaxBcd<uint64_t>(5)); + EXPECT_EQ(39, MaxBcd<uint64_t>(6)); + EXPECT_EQ(79, MaxBcd<uint64_t>(7)); + EXPECT_EQ(99, MaxBcd<uint64_t>(8)); + EXPECT_EQ(199, MaxBcd<uint64_t>(9)); + EXPECT_EQ(999, MaxBcd<uint64_t>(12)); + EXPECT_EQ(9999, MaxBcd<uint64_t>(16)); + EXPECT_EQ(999999, MaxBcd<uint64_t>(24)); + EXPECT_EQ(3999999999999999UL, MaxBcd<uint64_t>(62)); + EXPECT_EQ(7999999999999999UL, MaxBcd<uint64_t>(63)); + EXPECT_EQ(9999999999999999UL, MaxBcd<uint64_t>(64)); + // Max uint64_t is 18446744073709551616, which is big enough to hold a 76-bit + // BCD value. + EXPECT_EQ(19999999999999999UL, MaxBcd<uint64_t>(65)); + EXPECT_EQ(39999999999999999UL, MaxBcd<uint64_t>(66)); + EXPECT_EQ(99999999999999999UL, MaxBcd<uint64_t>(68)); + EXPECT_EQ(999999999999999999UL, MaxBcd<uint64_t>(72)); + EXPECT_EQ(9999999999999999999UL, MaxBcd<uint64_t>(76)); +} + +TEST(IsBcd, Values) { + EXPECT_TRUE(IsBcd(0x00U)); + EXPECT_TRUE(IsBcd(0x12U)); + EXPECT_TRUE(IsBcd(0x91U)); + EXPECT_TRUE(IsBcd(0x99U)); + EXPECT_TRUE(IsBcd(uint8_t{0x00})); + EXPECT_TRUE(IsBcd(uint8_t{0x99})); + EXPECT_TRUE(IsBcd(uint16_t{0x0000})); + EXPECT_TRUE(IsBcd(uint16_t{0x9999})); + EXPECT_TRUE(IsBcd(0x9999999999999999UL)); + EXPECT_FALSE(IsBcd(uint8_t{0x0a})); + EXPECT_FALSE(IsBcd(uint8_t{0xa0})); + EXPECT_FALSE(IsBcd(uint8_t{0xff})); + EXPECT_FALSE(IsBcd(uint16_t{0x0a00})); + EXPECT_FALSE(IsBcd(uint16_t{0x000a})); + EXPECT_FALSE(IsBcd(0x999999999999999aUL)); + EXPECT_FALSE(IsBcd(0xaUL)); + EXPECT_FALSE(IsBcd(0xa000000000000000UL)); + EXPECT_FALSE(IsBcd(0xf000000000000000UL)); + EXPECT_FALSE(IsBcd(0xffffffffffffffffUL)); +} + +TEST(BcdView, ValueType) { + using BitBlockType = BitBlockN<64>; + EXPECT_TRUE( + (::std::is_same<uint8_t, BcdView<ViewParameters<8>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint8_t, BcdView<ViewParameters<6>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint16_t, BcdView<ViewParameters<9>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint16_t, BcdView<ViewParameters<16>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint32_t, BcdView<ViewParameters<17>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint32_t, BcdView<ViewParameters<32>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint64_t, BcdView<ViewParameters<33>, + BitBlockType>::ValueType>::value)); + EXPECT_TRUE( + (::std::is_same<uint64_t, BcdView<ViewParameters<64>, + BitBlockType>::ValueType>::value)); +} + +TEST(BcdView, CouldWriteValue) { + EXPECT_TRUE((BcdView<ViewParameters<64>, int>::CouldWriteValue(0))); + EXPECT_TRUE( + (BcdView<ViewParameters<64>, int>::CouldWriteValue(9999999999999999))); + EXPECT_FALSE( + (BcdView<ViewParameters<64>, int>::CouldWriteValue(10000000000000000))); + EXPECT_FALSE(( + BcdView<ViewParameters<64>, int>::CouldWriteValue(0xffffffffffffffffUL))); + EXPECT_FALSE( + (BcdView<ViewParameters<48>, int>::CouldWriteValue(9999999999999999))); + EXPECT_TRUE( + (BcdView<ViewParameters<48>, int>::CouldWriteValue(999999999999))); + EXPECT_TRUE((BcdView<ViewParameters<48>, int>::CouldWriteValue(0))); + EXPECT_FALSE((BcdView<ViewParameters<48>, int>::CouldWriteValue( + (0xffUL << 48) + 999999999999))); + EXPECT_FALSE( + (BcdView<ViewParameters<48>, int>::CouldWriteValue(10000000000000000))); + EXPECT_FALSE(( + BcdView<ViewParameters<48>, int>::CouldWriteValue(0xffffffffffffffffUL))); +} + +template <size_t kBits> +using BcdViewN = BcdView<ViewParameters<kBits>, BitBlockN<kBits>>; + +TEST(BcdView, ReadAndWriteWithSufficientBuffer) { + ::std::vector<uint8_t> bytes = { + {0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08}}; + auto bcd64_view = + BcdViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}}; + EXPECT_EQ(910111213141516UL, bcd64_view.Read()); + EXPECT_EQ(910111213141516UL, bcd64_view.UncheckedRead()); + bcd64_view.Write(1615141312111009); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08}}), + bytes); + bcd64_view.UncheckedWrite(910111213141516UL); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08}}), + bytes); + EXPECT_TRUE(bcd64_view.TryToWrite(1615141312111009)); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08}}), + bytes); + EXPECT_TRUE(bcd64_view.Ok()); + EXPECT_TRUE(bcd64_view.IsComplete()); +} + +TEST(BcdView, ReadAndWriteWithInsufficientBuffer) { + ::std::vector<uint8_t> bytes = { + {0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08}}; + auto bcd64_view = + BcdViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 4}}}; + EXPECT_DEATH(bcd64_view.Read(), ""); + EXPECT_EQ(910111213141516UL, bcd64_view.UncheckedRead()); + EXPECT_DEATH(bcd64_view.Write(1615141312111009), ""); + EXPECT_FALSE(bcd64_view.TryToWrite(1615141312111009)); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08}}), + bytes); + bcd64_view.UncheckedWrite(1615141312111009); + EXPECT_EQ((::std::vector<uint8_t>{ + {0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08}}), + bytes); + EXPECT_FALSE(bcd64_view.Ok()); + EXPECT_FALSE(bcd64_view.IsComplete()); +} + +TEST(BcdView, NonPowerOfTwoSize) { + ::std::vector<uint8_t> bytes = {{0x16, 0x15, 0x14, 0x13}}; + auto bcd24_view = + BcdViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}}; + EXPECT_EQ(141516, bcd24_view.Read()); + EXPECT_EQ(141516, bcd24_view.UncheckedRead()); + bcd24_view.Write(161514); + EXPECT_EQ((::std::vector<uint8_t>{{0x14, 0x15, 0x16, 0x13}}), bytes); + EXPECT_DEATH(bcd24_view.Write(1000000), ""); + bcd24_view.UncheckedWrite(1000000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x13}}), bytes); + bcd24_view.UncheckedWrite(141516); + EXPECT_EQ((::std::vector<uint8_t>{{0x16, 0x15, 0x14, 0x13}}), bytes); + EXPECT_TRUE(bcd24_view.Ok()); + EXPECT_TRUE(bcd24_view.IsComplete()); +} + +TEST(BcdView, NonPowerOfTwoSizeInsufficientBuffer) { + ::std::vector<uint8_t> bytes = {{0x16, 0x15, 0x14, 0x13}}; + auto bcd24_view = + BcdViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 2}}}; + EXPECT_DEATH(bcd24_view.Read(), ""); + EXPECT_EQ(141516, bcd24_view.UncheckedRead()); + EXPECT_DEATH(bcd24_view.Write(161514), ""); + bcd24_view.UncheckedWrite(161514); + EXPECT_EQ((::std::vector<uint8_t>{{0x14, 0x15, 0x16, 0x13}}), bytes); + bcd24_view.UncheckedWrite(1000000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x13}}), bytes); + EXPECT_FALSE(bcd24_view.Ok()); + EXPECT_FALSE(bcd24_view.IsComplete()); +} + +TEST(BcdView, NonByteSize) { + ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x80, 0x80}}; + auto bcd23_view = + BcdView<ViewParameters<23>, OffsetBitBlock<BitBlockN<24>>>{BitBlockN<24>{ + ReadWriteContiguousBuffer{bytes.data(), + 3}}.GetOffsetStorage<1, 0>(0, 23)}; + EXPECT_EQ(0x0, bcd23_view.Read()); + EXPECT_FALSE(bcd23_view.CouldWriteValue(800000)); + EXPECT_TRUE(bcd23_view.CouldWriteValue(799999)); + EXPECT_DEATH(bcd23_view.Write(800000), ""); + bcd23_view.Write(432198); + EXPECT_EQ((::std::vector<uint8_t>{{0x98, 0x21, 0xc3, 0x80}}), bytes); + bcd23_view.UncheckedWrite(800000); + EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x80, 0x80}}), bytes); + EXPECT_TRUE(bcd23_view.Ok()); + EXPECT_TRUE(bcd23_view.IsComplete()); +} + +TEST(BcdLittleEndianView, AllByteValues) { + uint8_t byte = 0; + auto bcd8_view = + BcdViewN<8>{BitBlockN<8>{ReadWriteContiguousBuffer{&byte, 1}}}; + for (int i = 0; i < 15; ++i) { + for (int j = 0; j < 15; ++j) { + byte = i * 16 + j; + if (i > 9 || j > 9) { + EXPECT_FALSE(bcd8_view.Ok()) << i << ", " << j; + } else { + EXPECT_TRUE(bcd8_view.Ok()) << i << ", " << j; + } + } + } +} + +} // namespace test +} // namespace prelude +} // namespace emboss
diff --git a/public/emboss_test_util.h b/public/emboss_test_util.h new file mode 100644 index 0000000..89f9855 --- /dev/null +++ b/public/emboss_test_util.h
@@ -0,0 +1,98 @@ +// 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. + +#ifndef EMBOSS_PUBLIC_EMBOSS_TEST_UTIL_H_ +#define EMBOSS_PUBLIC_EMBOSS_TEST_UTIL_H_ + +#include <cctype> +#include <iterator> +#include <ostream> +#include <string> + +#include "public/emboss_text_util.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "third_party/absl/memory/memory.h" +#include "third_party/googletest/googletest/include/gtest/internal/gtest-internal.h" + +namespace emboss { + +class EmbMatcher { + public: + template <typename ViewType> + explicit EmbMatcher(ViewType compare_to) + : compare_to_ok_(compare_to.Ok()), + compare_to_lines_(SplitToLines( + compare_to_ok_ ? WriteToString(compare_to, MultilineText()) : "")) { + } + + template <typename ViewType> + bool MatchAndExplain(ViewType compare_from, + ::testing::MatchResultListener* listener) const { + if (!compare_to_ok_) { + *listener << "View for comparison to is not OK."; + return false; + } + + if (!compare_from.Ok()) { + *listener << "View for comparison from is not OK."; + return false; + } + + const auto compare_from_lines = + SplitToLines(WriteToString(compare_from, MultilineText())); + if (compare_from_lines != compare_to_lines_) { + *listener << "\n" + << ::testing::internal::edit_distance::CreateUnifiedDiff( + compare_to_lines_, compare_from_lines); + return false; + } + + return true; + } + + // Describes the property of a value matching this matcher. + void DescribeTo(::std::ostream* os) const { *os << "are equal"; } + + // Describes the property of a value NOT matching this matcher. + void DescribeNegationTo(::std::ostream* os) const { *os << "are NOT equal"; } + + private: + // Splits the given string on '\n' boundaries and returns a vector of those + // strings. + ::std::vector<::std::string> SplitToLines(const ::std::string& input) const { + constexpr char kNewLine = '\n'; + + ::std::stringstream ss(input); + ss.ignore(::std::numeric_limits<::std::streamsize>::max(), kNewLine); + + ::std::vector<::std::string> lines; + for (::std::string line; ::std::getline(ss, line, kNewLine);) { + lines.push_back(::std::move(line)); + } + return lines; + } + + const bool compare_to_ok_; + const ::std::vector<::std::string> compare_to_lines_; +}; + +template <typename ViewType> +::testing::PolymorphicMatcher<EmbMatcher> EqualsEmb(ViewType view) { + return ::testing::MakePolymorphicMatcher(EmbMatcher(view)); +} + +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_TEST_UTIL_H_
diff --git a/public/emboss_test_util_test.cc b/public/emboss_test_util_test.cc new file mode 100644 index 0000000..71a34fe --- /dev/null +++ b/public/emboss_test_util_test.cc
@@ -0,0 +1,134 @@ +// 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. + +#include "public/emboss_test_util.h" + +#include "testdata/complex_structure.emb.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +namespace emboss { +namespace test { +namespace { + +class EmbossTestUtilTest : public ::testing::Test { + protected: + EmbossTestUtilTest() { b_.s().Write(1); } + std::array<uint8, 64> buf_a_{}; + ::emboss_test::ComplexWriter a_{&buf_a_}; + std::array<char, 64> buf_b_{}; + ::emboss_test::ComplexWriter b_{&buf_b_}; +}; + +TEST_F(EmbossTestUtilTest, EqualsEmb) { + EXPECT_THAT(a_, EqualsEmb(a_)); + EXPECT_THAT(b_, EqualsEmb(b_)); + + EXPECT_THAT(a_, ::testing::Not(EqualsEmb(b_))); + EXPECT_THAT(b_, ::testing::Not(EqualsEmb(a_))); +} + +TEST_F(EmbossTestUtilTest, NotOkView) { + auto null_view = ::emboss_test::ComplexView(nullptr); + EXPECT_THAT(a_, ::testing::Not(EqualsEmb(null_view))); + EXPECT_THAT(b_, ::testing::Not(EqualsEmb(null_view))); + EXPECT_THAT(null_view, ::testing::Not(EqualsEmb(null_view))); + EXPECT_THAT(null_view, ::testing::Not(EqualsEmb(a_))); + EXPECT_THAT(null_view, ::testing::Not(EqualsEmb(b_))); +} + +TEST_F(EmbossTestUtilTest, NotOkViewMatcherDescribe) { + auto null_view = ::emboss_test::ComplexView(nullptr); + + ::testing::StringMatchResultListener listener; + EqualsEmb(a_).impl().MatchAndExplain(null_view, &listener); + EXPECT_EQ(listener.str(), "View for comparison from is not OK."); + + listener.Clear(); + EqualsEmb(null_view).impl().MatchAndExplain(a_, &listener); + EXPECT_EQ(listener.str(), "View for comparison to is not OK."); +} + +TEST_F(EmbossTestUtilTest, MatcherDescribeEquivalent) { + ::std::stringstream ss; + EqualsEmb(a_).impl().DescribeTo(&ss); + EXPECT_EQ(ss.str(), "are equal"); +} + +TEST_F(EmbossTestUtilTest, MatcherDescribeNotEquivalent) { + ::std::stringstream ss; + EqualsEmb(a_).impl().DescribeNegationTo(&ss); + EXPECT_EQ(ss.str(), "are NOT equal"); +} + +TEST_F(EmbossTestUtilTest, MatcherExplainEquivalent) { + ::testing::StringMatchResultListener listener; + + EqualsEmb(a_).impl().MatchAndExplain(a_, &listener); + EXPECT_EQ(listener.str(), ""); + + EqualsEmb(b_).impl().MatchAndExplain(b_, &listener); + EXPECT_EQ(listener.str(), ""); +} + +TEST_F(EmbossTestUtilTest, MatcherExplainNotEquivalent) { + ::testing::StringMatchResultListener listener; + EqualsEmb(a_).impl().MatchAndExplain(b_, &listener); + EXPECT_EQ(listener.str(), R"( +@@ -1,3 +1,3 @@ +- s: 0 # 0x0 ++ s: 1 # 0x1 + u: 0 # 0x0 + i: 0 # 0x0 +@@ +4,34 @@ + b: 0 # 0x0 + a: { ++ [0]: { ++ [0]: { ++ a: { ++ x: 0 # 0x0 ++ l: 0 # 0x0 ++ h: 0 # 0x0 ++ } ++ } ++ [1]: { ++ a: { ++ x: 0 # 0x0 ++ l: 0 # 0x0 ++ h: 0 # 0x0 ++ } ++ } ++ [2]: { ++ a: { ++ x: 0 # 0x0 ++ l: 0 # 0x0 ++ h: 0 # 0x0 ++ } ++ } ++ [3]: { ++ a: { ++ x: 0 # 0x0 ++ l: 0 # 0x0 ++ h: 0 # 0x0 ++ } ++ } ++ } + } + a0: 0 # 0x0 +)"); +} + +} // namespace +} // namespace test +} // namespace emboss
diff --git a/public/emboss_text_util.h b/public/emboss_text_util.h new file mode 100644 index 0000000..6ed1e05 --- /dev/null +++ b/public/emboss_text_util.h
@@ -0,0 +1,798 @@ +// 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 functionality related to Emboss text output. +#ifndef EMBOSS_PUBLIC_EMBOSS_TEXT_UTIL_H_ +#define EMBOSS_PUBLIC_EMBOSS_TEXT_UTIL_H_ + +#include <array> +#include <climits> +#include <cmath> +#include <cstdio> +#include <cstring> +#include <limits> +#include <sstream> +#include <string> +#include <vector> + +#include "public/emboss_defines.h" + +namespace emboss { + +// TextOutputOptions are used to configure text output. Typically, one can just +// use a default TextOutputOptions() (for compact output) or MultilineText() +// (for reasonable formatted output). +class TextOutputOptions final { + public: + TextOutputOptions() = default; + + TextOutputOptions PlusOneIndent() const { + TextOutputOptions result = *this; + result.current_indent_ += indent(); + return result; + } + + TextOutputOptions Multiline(bool new_value) const { + TextOutputOptions result = *this; + result.multiline_ = new_value; + return result; + } + + TextOutputOptions WithIndent(::std::string new_value) const { + TextOutputOptions result = *this; + result.indent_ = ::std::move(new_value); + return result; + } + + TextOutputOptions WithComments(bool new_value) const { + TextOutputOptions result = *this; + result.comments_ = new_value; + return result; + } + + TextOutputOptions WithDigitGrouping(bool new_value) const { + TextOutputOptions result = *this; + result.digit_grouping_ = new_value; + return result; + } + + TextOutputOptions WithNumericBase(int new_value) const { + TextOutputOptions result = *this; + result.numeric_base_ = new_value; + return result; + } + + ::std::string current_indent() const { return current_indent_; } + ::std::string indent() const { return indent_; } + bool multiline() const { return multiline_; } + bool digit_grouping() const { return digit_grouping_; } + bool comments() const { return comments_; } + uint8_t numeric_base() const { return numeric_base_; } + + private: + ::std::string indent_; + ::std::string current_indent_; + bool comments_ = false; + bool multiline_ = false; + bool digit_grouping_ = false; + uint8_t numeric_base_ = 10; +}; + +namespace support { + +// TextOutputStream puts a stream-like interface onto a std::string, for use by +// DumpToTextStream. It is used by UpdateFromText(). +class TextOutputStream final { + public: + inline explicit TextOutputStream() = default; + + inline void Write(const ::std::string &text) { + text_.write(text.data(), text.size()); + } + + inline void Write(const char *text) { text_.write(text, strlen(text)); } + + inline void Write(const char c) { text_.put(c); } + + inline ::std::string Result() { return text_.str(); } + + private: + ::std::ostringstream text_; +}; + +// DecodeInteger decodes an integer from a string. This is very similar to the +// many, many existing integer decode routines in the world, except that a) it +// accepts integers in any Emboss format, and b) it can run in environments that +// do not support std::istream or Google's number conversion routines. +// +// Ideally, this would be replaced by someone else's code. +template <class IntType> +bool DecodeInteger(const ::std::string &text, IntType *result) { + IntType accumulator = 0; + IntType base = 10; + bool negative = false; + unsigned offset = 0; + if (::std::is_signed<IntType>::value && text.size() >= 1 + offset && + text[offset] == '-') { + negative = true; + offset += 1; + } + if (text.size() >= 2 + offset && text[offset] == '0') { + if (text[offset + 1] == 'x' || text[offset + 1] == 'X') { + base = 16; + offset += 2; + } else if (text[offset + 1] == 'b' || text[offset + 1] == 'B') { + base = 2; + offset += 2; + } + } + // "", "0x", "0b", "-", "-0x", and "-0b" are not valid numbers. + if (offset == text.size()) return false; + for (; offset < text.size(); ++offset) { + char c = text[offset]; + IntType digit = 0; + if (c == '_') { + if (offset == 0) { + return false; + } + continue; + } else if (c >= '0' && c <= '9') { + digit = c - '0'; + } else if (c >= 'A' && c <= 'F') { + digit = c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + digit = c - 'a' + 10; + } else { + return false; + } + if (digit >= base) { + return false; + } + if (negative) { + if (accumulator < + (::std::numeric_limits<IntType>::min() + digit) / base) { + return false; + } + accumulator = accumulator * base - digit; + } else { + if (accumulator > + (::std::numeric_limits<IntType>::max() - digit) / base) { + return false; + } + accumulator = accumulator * base + digit; + } + } + *result = accumulator; + return true; +} + +template <class Stream> +bool DiscardWhitespace(Stream *stream) { + char c; + bool in_comment = false; + do { + if (!stream->Read(&c)) return true; + if (c == '#') in_comment = true; + if (c == '\r' || c == '\n') in_comment = false; + } while (in_comment || c == ' ' || c == '\t' || c == '\n' || c == '\r'); + return stream->Unread(c); +} + +template <class Stream> +bool ReadToken(Stream *stream, ::std::string *token) { + ::std::vector<char> result; + char c; + if (!DiscardWhitespace(stream)) return false; + if (!stream->Read(&c)) { + *token = ""; + return true; + } + + const char *const punctuation = ":{}[],"; + if (strchr(punctuation, c) != nullptr) { + *token = ::std::string(1, c); + return true; + } else { + // TODO(bolms): Only allow alphanumeric characters here? + do { + result.push_back(c); + if (!stream->Read(&c)) { + *token = ::std::string(&result[0], result.size()); + return true; + } + } while (c != ' ' && c != '\t' && c != '\n' && c != '\r' && c != '#' && + strchr(punctuation, c) == nullptr); + if (!stream->Unread(c)) return false; + *token = ::std::string(&result[0], result.size()); + return true; + } +} + +template <class Stream, class View> +bool ReadIntegerFromTextStream(View *view, Stream *stream) { + ::std::string token; + if (!::emboss::support::ReadToken(stream, &token)) return false; + if (token.empty()) return false; + typename View::ValueType value; + if (!::emboss::support::DecodeInteger(token, &value)) return false; + return view->TryToWrite(value); +} + +// WriteIntegerToTextStream encodes the given value in base 2, 10, or 16, with +// or without digit group separators ('_'), and then calls stream->Write() with +// a char * argument that is a C-style null-terminated string of the encoded +// number. +// +// As with DecodeInteger, above, it would be nice to be able to replace this +// with someone else's code, but I (bolms@) was unable to find anything in +// standard C++ that would encode numbers in binary, nothing that would add +// digit separators to hex numbers, and nothing that would use '_' for digit +// separators. +template <class Stream, typename IntegralType> +void WriteIntegerToTextStream(IntegralType value, Stream *stream, uint8_t base, + bool digit_grouping) { + static_assert(::std::numeric_limits< + typename ::std::remove_cv<IntegralType>::type>::is_integer, + "WriteIntegerToTextStream only supports integer types."); + static_assert( + !::std::is_same<bool, + typename ::std::remove_cv<IntegralType>::type>::value, + "WriteIntegerToTextStream only supports integer types."); + EMBOSS_CHECK(base == 10 || base == 2 || base == 16); + const char *const digits = "0123456789abcdef"; + const int grouping = base == 10 ? 3 : base == 16 ? 4 : 8; + // The maximum size 32-bit number is -2**31, which is: + // + // -0b10000000_00000000_00000000_00000000 (38 chars) + // -2_147_483_648 (14 chars) + // -0x8000_0000 (12 chars) + // + // Likewise, the maximum size 8-bit number is -128, which is: + // -0b10000000 (11 chars) + // -128 (4 chars) + // -0x80 (5 chars) + // + // Binary with separators is always the longest value: 9 chars per 8 bits, + // minus 1 char for the '_' that does not appear at the front of the number, + // plus 2 chars for "0b", plus 1 char for '-', plus 1 extra char for the + // trailing '\0', which is (sizeof value) * CHAR_BIT * 9 / 8 - 1 + 2 + 1 + 1. + const int buffer_size = (sizeof value) * CHAR_BIT * 9 / 8 + 3; + char buffer[buffer_size]; + buffer[buffer_size - 1] = '\0'; + int next_char = buffer_size - 2; + if (value == 0) { + EMBOSS_DCHECK_GE(next_char, 0); + buffer[next_char] = digits[0]; + --next_char; + } + int sign = value < 0 ? -1 : 1; + int digit_count = 0; + auto buffer_char = [&](char c) { + EMBOSS_DCHECK_GE(next_char, 0); + buffer[next_char] = c; + --next_char; + }; + if (value < 0) { + if (value == ::std::numeric_limits<decltype(value)>::lowest()) { + // The minimum negative two's-complement value has no corresponding + // positive value, so 'value = -value' is not useful in that case. + // Instead, we do some trickery to buffer the lowest-order digit here. + auto digit = -(value + 1) % base + 1; + value = -(value + 1) / base; + if (digit == base) { + digit = 0; + ++value; + } + buffer_char(digits[digit]); + ++digit_count; + } else { + value = -value; + } + } + while (value > 0) { + if (digit_count && digit_count % grouping == 0 && digit_grouping) { + buffer_char('_'); + } + buffer_char(digits[value % base]); + value /= base; + ++digit_count; + } + if (base == 16) { + buffer_char('x'); + buffer_char('0'); + } else if (base == 2) { + buffer_char('b'); + buffer_char('0'); + } + if (sign < 0) { + buffer_char('-'); + } + + stream->Write(buffer + 1 + next_char); +} + +// Writes an integer value in the base given in options, plus an optional +// comment with the same value in a second base. This is used for the common +// output format of IntView, UIntView, and BcdView. +template <class Stream, class View> +void WriteIntegerViewToTextStream(View *view, Stream *stream, + const TextOutputOptions &options) { + WriteIntegerToTextStream(view->Read(), stream, options.numeric_base(), + options.digit_grouping()); + if (options.comments()) { + stream->Write(" # "); + WriteIntegerToTextStream(view->Read(), stream, + options.numeric_base() == 10 ? 16 : 10, + options.digit_grouping()); + } +} + +// The TextOutputOptions parameter is present so that it can be passed in by +// generated code that uses the same form for WriteBooleanViewToTextStream, +// WriteIntegerViewToTextStream, and WriteEnumViewToTextStream. +template <class Stream, class View> +void WriteBooleanViewToTextStream(View *view, Stream *stream, + const TextOutputOptions &) { + if (view->Read()) { + stream->Write("true"); + } else { + stream->Write("false"); + } +} + +// FloatConstants holds various masks for working with IEEE754-compatible +// floating-point values at a bit level. These are mostly used here to +// implement text format for NaNs, preserving the NaN payload so that the text +// format can (in theory) provide a bit-exact round-trip through the text +// format. +template <class Float> +struct FloatConstants; + +template <> +struct FloatConstants<float> { + static_assert(sizeof(float) == 4, "Emboss requires 32-bit float."); + using MatchingIntegerType = ::std::uint32_t; + static constexpr MatchingIntegerType kMantissaMask() { return 0x7fffffU; } + static constexpr MatchingIntegerType kExponentMask() { return 0x7f800000U; } + static constexpr MatchingIntegerType kSignMask() { return 0x80000000U; } + static constexpr int kPrintfPrecision() { return 9; } + static constexpr const char *kScanfFormat() { return "%f%n"; } +}; + +template <> +struct FloatConstants<double> { + static_assert(sizeof(double) == 8, "Emboss requires 64-bit double."); + using MatchingIntegerType = ::std::uint64_t; + static constexpr MatchingIntegerType kMantissaMask() { return 0xfffffffffffffUL; } + static constexpr MatchingIntegerType kExponentMask() { return 0x7ff0000000000000UL; } + static constexpr MatchingIntegerType kSignMask() { return 0x8000000000000000UL; } + static constexpr int kPrintfPrecision() { return 17; } + static constexpr const char *kScanfFormat() { return "%lf%n"; } +}; + +// Decodes a floating-point number from text. +template <class Float> +bool DecodeFloat(const ::std::string &token, Float *result) { + // The state of the world for reading floating-point values is somewhat better + // than the situation for writing them, but there are still a few bits that + // are underspecified. This function is the mirror of WriteFloatToTextStream, + // below, so it specifically decodes infinities and NaNs in the formats that + // Emboss uses. + // + // Because of the use of scanf here, this function accepts hex floating-point + // values (0xh.hhhhpeee) *on some systems*. TODO(bolms): make hex float + // support universal. + + using UInt = typename FloatConstants<Float>::MatchingIntegerType; + + if (token.empty()) return false; + + // First, check for negative. + bool negative = token[0] == '-'; + + // Second, check for NaN. + ::std::size_t i = token[0] == '-' || token[0] == '+' ? 1 : 0; + if (token.size() >= i + 3 && (token[i] == 'N' || token[i] == 'n') && + (token[i + 1] == 'A' || token[i + 1] == 'a') && + (token[i + 2] == 'N' || token[i + 2] == 'n')) { + UInt nan_payload; + if (token.size() >= i + 4) { + if (token[i + 3] == '(' && token[token.size() - 1] == ')') { + if (!DecodeInteger(token.substr(i + 4, token.size() - i - 5), + &nan_payload)) { + return false; + } + } else { + // NaN may not be followed by trailing characters other than a + // ()-enclosed payload. + return false; + } + } else { + // If no specific NaN was given, take a default NaN from the C++ standard + // library. Technically, a conformant C++ implementation might not have + // quiet_NaN(), but any IEEE754-based implementation should. + // + // It is tempting to just write the default NaN directly into the view and + // return success, but "-NaN" should be have its sign bit set, and there + // is no direct way to set the sign bit of a NaN, so there are fewer code + // paths if we extract the default NaN payload, then use it in the + // reconstruction step, below. + Float default_nan = ::std::numeric_limits<Float>::quiet_NaN(); + UInt bits; + ::std::memcpy(&bits, &default_nan, sizeof(bits)); + nan_payload = bits & FloatConstants<Float>::kMantissaMask(); + } + if (nan_payload == 0) { + // "NaN" with a payload of zero is actually the bit pattern for infinity; + // "NaN(0)" should not be an alias for "Inf". + return false; + } + if (nan_payload & (FloatConstants<Float>::kExponentMask() | + FloatConstants<Float>::kSignMask())) { + // The payload must be small enough to fit in the payload space; it must + // not overflow into the exponent or sign bits. + // + // Note that the DecodeInteger call which decoded the payload will return + // false if the payload would overflow the `UInt` type, so cases like + // "NaN(0x10000000000000000000000000000)" -- which are so big that they no + // longer interfere with the sign or exponent -- are caught above. + return false; + } + UInt bits = FloatConstants<Float>::kExponentMask(); + bits |= nan_payload; + if (negative) { + bits |= FloatConstants<Float>::kSignMask(); + } + ::std::memcpy(result, &bits, sizeof(bits)); + return true; + } + + // If the value is not NaN, check for infinity. + if (token.size() >= i + 3 && (token[i] == 'I' || token[i] == 'i') && + (token[i + 1] == 'N' || token[i + 1] == 'n') && + (token[i + 2] == 'F' || token[i + 2] == 'f')) { + if (token.size() > i + 3) { + // Infinity must be exactly "Inf" or "-Inf" (case insensitive). There + // must not be trailing characters. + return false; + } + // As with quiet_NaN(), a conforming C++ implementation might not have + // infinity(), but an IEEE 754-based implementation should. + if (negative) { + *result = -::std::numeric_limits<Float>::infinity(); + return true; + } else { + *result = ::std::numeric_limits<Float>::infinity(); + return true; + } + } + + // For non-NaN, non-Inf values, use the C scanf function, mirroring the use of + // printf for writing the value, below. + int chars_used = -1; + if (::std::sscanf(token.c_str(), FloatConstants<Float>::kScanfFormat(), result, + &chars_used) < 1) { + return false; + } + if (chars_used < 0 || + static_cast</**/ ::std::size_t>(chars_used) < token.size()) { + return false; + } + return true; +} + +// Decodes a floating-point number from a text stream and writes it to the +// specified view. +template <class Stream, class View> +bool ReadFloatFromTextStream(View *view, Stream *stream) { + ::std::string token; + if (!ReadToken(stream, &token)) return false; + typename View::ValueType value; + if (!DecodeFloat(token, &value)) return false; + return view->TryToWrite(value); +} + +template <class Stream, class Float> +void WriteFloatToTextStream(Float n, Stream *stream, + const TextOutputOptions &options) { + static_assert(::std::is_same<Float, float>::value || + ::std::is_same<Float, double>::value, + "WriteFloatToTextStream can only write float or double."); + // The state of the world w.r.t. rendering floating-points as decimal text is, + // ca. 2018, less than ideal. + // + // In C++ land, there is actually no stable facility in the standard library + // until to_chars() in C++17 -- which is not actually implemented yet in + // libc++. to_string(), the printf() family, and the iostreams system all + // respect the current locale. In most programs, the locale is permanently + // left on "C", but this is not guaranteed. to_string() also uses a fixed and + // rather unfortunate format. + // + // For integers, I (bolms@) chose to just implement custom read and write + // routines, but those routines are quite small and straightforward compared + // to floating point conversion. Even writing correct output is difficult, + // and writing correct and minimal output is the subject of a number of + // academic papers. + // + // For the moment, I'm just using snprintf("%.*g", 17, n), which is guaranteed + // to be read back as the same number, but can be longer than strictly + // necessary. + // + // TODO(bolms): Import a modified version of the double-to-string conversion + // from Swift's standard library, which appears to be best implementation + // currently available. + + if (::std::isnan(n)) { + // The printf format for NaN is just "NaN". In the interests of keeping + // things bit-exact, Emboss prints the exact NaN. + typename FloatConstants<Float>::MatchingIntegerType bits; + ::std::memcpy(&bits, &n, sizeof(bits)); + ::std::uint64_t nan_payload = bits & FloatConstants<Float>::kMantissaMask(); + ::std::uint64_t nan_sign = bits & FloatConstants<Float>::kSignMask(); + if (nan_sign) { + // NaN still has a sign bit, which is generally treated differently from + // the payload. There is no real "standard" text format for NaNs, but + // "-NaN" appears to be a common way of indicating a NaN with the sign bit + // set. + stream->Write("-NaN("); + } else { + stream->Write("NaN("); + } + // NaN payloads are always dumped in hex. Note that Emboss is treating the + // is_quiet/is_signal bit as just another bit in the payload. + WriteIntegerToTextStream(nan_payload, stream, 16, options.digit_grouping()); + stream->Write(")"); + return; + } + + if (::std::isinf(n)) { + if (n < 0.0) { + stream->Write("-Inf"); + } else { + stream->Write("Inf"); + } + return; + } + + // TODO(bolms): Should the current numeric base be honored here? Should there + // be a separate Float numeric base? + ::std::array<char, 30> buffer; + // TODO(bolms): Figure out how to get ::std::snprintf to work on + // microcontroller builds. + EMBOSS_CHECK_LE(static_cast</**/ ::std::size_t>( + ::snprintf(&(buffer[0]), buffer.size(), "%.*g", + FloatConstants<Float>::kPrintfPrecision(), + static_cast<double>(n)) + + 1), + buffer.size()); + stream->Write(&buffer[0]); + + // TODO(bolms): Support digit grouping. +} + +template <class Stream, class View> +void WriteEnumViewToTextStream(View *view, Stream *stream, + const TextOutputOptions &options) { + const char *name = TryToGetNameFromEnum(view->Read()); + if (name != nullptr) { + stream->Write(name); + } + // If the enum value has no known name, then write its numeric value + // instead. If it does have a known name, and comments are enabled on the + // output, then write the numeric value as a comment. + if (name == nullptr || options.comments()) { + if (name != nullptr) stream->Write(" # "); + WriteIntegerToTextStream( + static_cast< + typename ::std::underlying_type<typename View::ValueType>::type>( + view->Read()), + stream, options.numeric_base(), options.digit_grouping()); + } +} + +// Updates an array from a text stream. For an array of integers, the most +// basic form of the text format looks like: +// +// { 0, 1, 2 } +// +// However, the following are all acceptable and equivalent: +// +// { 0, 1, 2, } +// {0 1 2} +// { [2]: 2, [1]: 1, [0]: 0 } +// {[2]:2, [0]:0, 1} +// +// Formally, the array must be contained within braces ("{}"). Elements are +// represented as an optional index surrounded by brackets ("[]") followed by +// the text format of the element, followed by a single optional comma (","). +// If no index is present for the first element, the index 0 will be used. If +// no index is present for any elements after the first, the index one greater +// than the previous index will be used. +template <class Array, class Stream> +bool ReadArrayFromTextStream(Array *array, Stream *stream) { + // The text format allows any given index to be set more than once. In + // theory, this function could track indices and fail if an index were + // double-set, but doing so would require quite a bit of overhead, and + // O(array->ElementCount()) extra space in the worst case. It does not seem + // worth it to impose the runtime cost here. + size_t index = 0; + ::std::string brace; + // Read out the opening brace. + if (!ReadToken(stream, &brace)) return false; + if (brace != "{") return false; + for (;;) { + char c; + // Check for a closing brace; if present, success. + if (!DiscardWhitespace(stream)) return false; + if (!stream->Read(&c)) return false; + if (c == '}') return true; + + // If the element has an index, read it. + if (c == '[') { + ::std::string index_text; + if (!ReadToken(stream, &index_text)) return false; + if (!::emboss::support::DecodeInteger(index_text, &index)) return false; + ::std::string closing_bracket; + if (!ReadToken(stream, &closing_bracket)) return false; + if (closing_bracket != "]") return false; + ::std::string colon; + if (!ReadToken(stream, &colon)) return false; + if (colon != ":") return false; + } else { + if (!stream->Unread(c)) return false; + } + + // Read the element. + if (index >= array->ElementCount()) return false; + if (!(*array)[index].UpdateFromTextStream(stream)) return false; + ++index; + + // If there is a trailing comma, discard it. + if (!DiscardWhitespace(stream)) return false; + if (!stream->Read(&c)) return false; + if (c != ',') { + if (c != '}') return false; + if (!stream->Unread(c)) return false; + } + } +} + +// Writes an array to a text stream. This writes the array in a format +// compatible with ReadArrayFromTextStream, above. For multiline output, writes +// one element per line. +// +// TODO(bolms): Make the output for arrays of small elements (like bytes) much +// more compact. +// +// This will require several support functions like `MaxTextLength` on every +// view type, and will substantially increase the number of tests required for +// this function, but will make arrays of small elements much more readable. +template <class Array, class Stream> +void WriteArrayToTextStream(Array *array, Stream *stream, + const TextOutputOptions &options) { + TextOutputOptions element_options = options.PlusOneIndent(); + if (options.multiline()) { + stream->Write("{"); + WriteShorthandArrayCommentToTextStream(array, stream, element_options); + for (::std::size_t i = 0; i < array->ElementCount(); ++i) { + stream->Write("\n"); + stream->Write(element_options.current_indent()); + stream->Write("["); + // TODO(bolms): Put padding in here so that array elements start at the + // same column. + // + // TODO(bolms): (Maybe) figure out how to get padding to work so that + // elements with comments can have their comments align to the same + // column. + WriteIntegerToTextStream(i, stream, options.numeric_base(), + options.digit_grouping()); + stream->Write("]: "); + (*array)[i].WriteToTextStream(stream, element_options); + } + stream->Write("\n"); + stream->Write(options.current_indent()); + stream->Write("}"); + } else { + stream->Write("{"); + for (::std::size_t i = 0; i < array->ElementCount(); ++i) { + stream->Write(" "); + if (i % 8 == 0) { + stream->Write("["); + WriteIntegerToTextStream(i, stream, options.numeric_base(), + options.digit_grouping()); + stream->Write("]: "); + } + (*array)[i].WriteToTextStream(stream, element_options); + if (i < array->ElementCount() - 1) { + stream->Write(","); + } + } + stream->Write(" }"); + } +} + +// TextStream puts a stream-like interface onto a std::string, for use by +// UpdateFromTextStream. It is used by UpdateFromText(). +class TextStream final { + public: + // This template handles std::string, std::string_view, and absl::string_view. + template <class String> + inline explicit TextStream(const String &text) + : text_(text.data()), length_(text.size()) {} + + inline explicit TextStream(const char *text) + : text_(text), length_(strlen(text)) {} + + inline TextStream(const char *text, size_t length) + : text_(text), length_(length) {} + + inline bool Read(char *result) { + if (index_ >= length_) return false; + *result = text_[index_]; + ++index_; + return true; + } + + inline bool Unread(char c) { + if (index_ < 1) return false; + if (text_[index_ - 1] != c) return false; + --index_; + return true; + } + + private: + // It would be nice to use string_view here, but that's not available until + // C++17. + const char *text_ = nullptr; + size_t length_ = 0; + size_t index_ = 0; +}; + +} // namespace support + +// Returns a TextOutputOptions set for reasonable multi-line text output. +static inline TextOutputOptions MultilineText() { + return TextOutputOptions() + .Multiline(true) + .WithIndent(" ") + .WithComments(true) + .WithDigitGrouping(true); +} + +// TODO(bolms): Add corresponding ReadFromText*() verbs which enforce the +// constraint that all of a field's dependencies must be present in the text +// before the field itself is set. +template <typename EmbossViewType> +inline bool UpdateFromText(const EmbossViewType &view, + const ::std::string &text) { + auto text_stream = support::TextStream{text}; + return view.UpdateFromTextStream(&text_stream); +} + +template <typename EmbossViewType> +inline ::std::string WriteToString(const EmbossViewType &view, + TextOutputOptions options) { + support::TextOutputStream text_stream; + view.WriteToTextStream(&text_stream, options); + return text_stream.Result(); +} + +template <typename EmbossViewType> +inline ::std::string WriteToString(const EmbossViewType &view) { + return WriteToString(view, TextOutputOptions()); +} + +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_TEXT_UTIL_H_
diff --git a/public/emboss_text_util_test.cc b/public/emboss_text_util_test.cc new file mode 100644 index 0000000..4a0e4eb --- /dev/null +++ b/public/emboss_text_util_test.cc
@@ -0,0 +1,1114 @@ +// 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. + +#include "public/emboss_text_util.h" + +#include <cmath> +#include <limits> + +#include <gtest/gtest.h> + +namespace emboss { +namespace support { +namespace test { + +TEST(DecodeInteger, DecodeUInt8Decimal) { + uint8_t result; + EXPECT_TRUE(DecodeInteger("123", &result)); + EXPECT_EQ(123, result); + EXPECT_TRUE(DecodeInteger("0", &result)); + EXPECT_EQ(0, result); + EXPECT_TRUE(DecodeInteger("0123", &result)); + EXPECT_EQ(123, result); + EXPECT_TRUE(DecodeInteger("0_123", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("_12", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("1234", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12a", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12A", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12 ", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger(" 12", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12.", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12.0", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("256", &result)); + EXPECT_EQ(123, result); + EXPECT_TRUE(DecodeInteger("128", &result)); + EXPECT_EQ(128, result); + EXPECT_FALSE(DecodeInteger("-0", &result)); + EXPECT_EQ(128, result); + EXPECT_TRUE(DecodeInteger("255", &result)); + EXPECT_EQ(255, result); +} + +TEST(DecodeInteger, DecodeInt8Decimal) { + int8_t result; + EXPECT_TRUE(DecodeInteger("123", &result)); + EXPECT_EQ(123, result); + EXPECT_TRUE(DecodeInteger("0", &result)); + EXPECT_EQ(0, result); + EXPECT_TRUE(DecodeInteger("0123", &result)); + EXPECT_EQ(123, result); + EXPECT_TRUE(DecodeInteger("0_123", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("_12", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("1234", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12a", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12A", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12 ", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger(" 12", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12.", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("12.0", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("256", &result)); + EXPECT_EQ(123, result); + EXPECT_FALSE(DecodeInteger("128", &result)); + EXPECT_EQ(123, result); + EXPECT_TRUE(DecodeInteger("-0", &result)); + EXPECT_EQ(0, result); + EXPECT_TRUE(DecodeInteger("127", &result)); + EXPECT_EQ(127, result); + EXPECT_TRUE(DecodeInteger("-127", &result)); + EXPECT_EQ(-127, result); + EXPECT_TRUE(DecodeInteger("-128", &result)); + EXPECT_EQ(-128, result); + EXPECT_FALSE(DecodeInteger("0-127", &result)); + EXPECT_EQ(-128, result); + EXPECT_FALSE(DecodeInteger("- 127", &result)); + EXPECT_EQ(-128, result); +} + +TEST(DecodeInteger, DecodeUInt8Hex) { + uint8_t result; + EXPECT_TRUE(DecodeInteger("0x23", &result)); + EXPECT_EQ(0x23, result); + EXPECT_TRUE(DecodeInteger("0x0", &result)); + EXPECT_EQ(0x0, result); + EXPECT_TRUE(DecodeInteger("0xff", &result)); + EXPECT_EQ(0xff, result); + EXPECT_TRUE(DecodeInteger("0xFE", &result)); + EXPECT_EQ(0xfe, result); + EXPECT_TRUE(DecodeInteger("0xFd", &result)); + EXPECT_EQ(0xfd, result); + EXPECT_TRUE(DecodeInteger("0XeC", &result)); + EXPECT_EQ(0xec, result); + EXPECT_TRUE(DecodeInteger("0x012", &result)); + EXPECT_EQ(0x12, result); + EXPECT_TRUE(DecodeInteger("0x0_0023", &result)); + EXPECT_EQ(0x23, result); + EXPECT_TRUE(DecodeInteger("0x_0023", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0x100", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0x", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0x0x0", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0x1g", &result)); + EXPECT_EQ(0x23, result); +} + +TEST(DecodeInteger, DecodeUInt8Binary) { + uint8_t result; + EXPECT_TRUE(DecodeInteger("0b10100101", &result)); + EXPECT_EQ(0xa5, result); + EXPECT_TRUE(DecodeInteger("0b0", &result)); + EXPECT_EQ(0x0, result); + EXPECT_TRUE(DecodeInteger("0B1", &result)); + EXPECT_EQ(0x1, result); + EXPECT_TRUE(DecodeInteger("0b11111111", &result)); + EXPECT_EQ(0xff, result); + EXPECT_TRUE(DecodeInteger("0b011111110", &result)); + EXPECT_EQ(0xfe, result); + EXPECT_TRUE(DecodeInteger("0b00_0010_0011", &result)); + EXPECT_EQ(0x23, result); + EXPECT_TRUE(DecodeInteger("0b_0010_0011", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b100000000", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b0b0", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b12", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("-0b0", &result)); + EXPECT_EQ(0x23, result); +} + +TEST(DecodeInteger, DecodeInt8Binary) { + int8_t result; + EXPECT_TRUE(DecodeInteger("0b01011010", &result)); + EXPECT_EQ(0x5a, result); + EXPECT_TRUE(DecodeInteger("0b0", &result)); + EXPECT_EQ(0x0, result); + EXPECT_TRUE(DecodeInteger("0B1", &result)); + EXPECT_EQ(0x1, result); + EXPECT_TRUE(DecodeInteger("0b1111111", &result)); + EXPECT_EQ(0x7f, result); + EXPECT_TRUE(DecodeInteger("0b01111110", &result)); + EXPECT_EQ(0x7e, result); + EXPECT_TRUE(DecodeInteger("0b00_0010_0011", &result)); + EXPECT_EQ(0x23, result); + EXPECT_TRUE(DecodeInteger("0b_0010_0011", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b100000000", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("-0b", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b0b0", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b12", &result)); + EXPECT_EQ(0x23, result); + EXPECT_FALSE(DecodeInteger("0b10000000", &result)); + EXPECT_EQ(0x23, result); + EXPECT_TRUE(DecodeInteger("-0b1111111", &result)); + EXPECT_EQ(-0x7f, result); + EXPECT_TRUE(DecodeInteger("-0b10000000", &result)); + EXPECT_EQ(-0x80, result); + EXPECT_FALSE(DecodeInteger("-0b10000001", &result)); + EXPECT_EQ(-0x80, result); + EXPECT_TRUE(DecodeInteger("-0b0", &result)); + EXPECT_EQ(0x0, result); +} + +TEST(DecodeInteger, DecodeUInt16) { + uint16_t result; + EXPECT_TRUE(DecodeInteger("65535", &result)); + EXPECT_EQ(65535, result); + EXPECT_FALSE(DecodeInteger("65536", &result)); + EXPECT_EQ(65535, result); +} + +TEST(DecodeInteger, DecodeInt16) { + int16_t result; + EXPECT_TRUE(DecodeInteger("32767", &result)); + EXPECT_EQ(32767, result); + EXPECT_FALSE(DecodeInteger("32768", &result)); + EXPECT_EQ(32767, result); + EXPECT_TRUE(DecodeInteger("-32768", &result)); + EXPECT_EQ(-32768, result); + EXPECT_FALSE(DecodeInteger("-32769", &result)); + EXPECT_EQ(-32768, result); +} + +TEST(DecodeInteger, DecodeUInt32) { + uint32_t result; + EXPECT_TRUE(DecodeInteger("4294967295", &result)); + EXPECT_EQ(4294967295U, result); + EXPECT_FALSE(DecodeInteger("4294967296", &result)); + EXPECT_EQ(4294967295U, result); +} + +TEST(DecodeInteger, DecodeInt32) { + int32_t result; + EXPECT_TRUE(DecodeInteger("2147483647", &result)); + EXPECT_EQ(2147483647, result); + EXPECT_FALSE(DecodeInteger("2147483648", &result)); + EXPECT_EQ(2147483647, result); + EXPECT_FALSE(DecodeInteger("4294967295", &result)); + EXPECT_EQ(2147483647, result); + EXPECT_TRUE(DecodeInteger("-2147483648", &result)); + EXPECT_EQ(-2147483647 - 1, result); + EXPECT_FALSE(DecodeInteger("-2147483649", &result)); + EXPECT_EQ(-2147483647 - 1, result); +} + +TEST(DecodeInteger, DecodeUInt64) { + uint64_t result; + EXPECT_TRUE(DecodeInteger("18446744073709551615", &result)); + EXPECT_EQ(18446744073709551615ULL, result); + EXPECT_FALSE(DecodeInteger("18446744073709551616", &result)); + EXPECT_EQ(18446744073709551615ULL, result); +} + +TEST(DecodeInteger, DecodeInt64) { + int64_t result; + EXPECT_TRUE(DecodeInteger("9223372036854775807", &result)); + EXPECT_EQ(9223372036854775807LL, result); + EXPECT_FALSE(DecodeInteger("9223372036854775808", &result)); + EXPECT_EQ(9223372036854775807LL, result); + EXPECT_FALSE(DecodeInteger("18446744073709551615", &result)); + EXPECT_EQ(9223372036854775807LL, result); + EXPECT_TRUE(DecodeInteger("-9223372036854775808", &result)); + EXPECT_EQ(-9223372036854775807LL - 1LL, result); + EXPECT_FALSE(DecodeInteger("-9223372036854775809", &result)); + EXPECT_EQ(-9223372036854775807LL - 1LL, result); +} + +TEST(TextStream, Construction) { + std::string string_text = "ab"; + auto text_stream = TextStream(string_text); + char result; + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('a', result); + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('b', result); + EXPECT_FALSE(text_stream.Read(&result)); + + const char *c_string = "cd"; + text_stream = TextStream(c_string); + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('c', result); + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('d', result); + EXPECT_FALSE(text_stream.Read(&result)); + + const char *long_c_string = "efghi"; + text_stream = TextStream(long_c_string, 2); + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('e', result); + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('f', result); + EXPECT_FALSE(text_stream.Read(&result)); +} + +TEST(TextStream, Methods) { + auto text_stream = TextStream{"abc"}; + + EXPECT_FALSE(text_stream.Unread('d')); + char result; + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('a', result); + + EXPECT_FALSE(text_stream.Unread('e')); + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('b', result); + + EXPECT_TRUE(text_stream.Unread('b')); + result = 'f'; + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('b', result); + + EXPECT_TRUE(text_stream.Read(&result)); + EXPECT_EQ('c', result); + + result = 'g'; + EXPECT_FALSE(text_stream.Read(&result)); + EXPECT_EQ('g', result); + + auto empty_text_stream = TextStream{""}; + EXPECT_FALSE(empty_text_stream.Read(&result)); + EXPECT_EQ('g', result); +} + +TEST(ReadToken, ReadsToken) { + auto text_stream = TextStream{"abc"}; + ::std::string result; + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("abc", result); + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("", result); + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("", result); +} + +TEST(ReadToken, ReadsTwoTokens) { + auto text_stream = TextStream{"abc def"}; + ::std::string result; + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("abc", result); + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("def", result); + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("", result); +} + +TEST(ReadToken, SkipsInitialWhitespace) { + auto text_stream = TextStream{" \t\r\r\n\t\r abc def"}; + ::std::string result; + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("abc", result); + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("def", result); + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("", result); +} + +TEST(ReadToken, SkipsComments) { + auto text_stream = TextStream{" #comment##\r#comment\n abc #c\n def"}; + ::std::string result; + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("abc", result); + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("def", result); + EXPECT_TRUE(ReadToken(&text_stream, &result)); + EXPECT_EQ("", result); +} + +TEST(TextOutputOptions, Defaults) { + TextOutputOptions options; + EXPECT_EQ("", options.current_indent()); + EXPECT_EQ("", options.indent()); + EXPECT_FALSE(options.multiline()); + EXPECT_FALSE(options.comments()); + EXPECT_FALSE(options.digit_grouping()); + EXPECT_EQ(10, options.numeric_base()); +} + +TEST(TextOutputOptions, WithIndent) { + TextOutputOptions options; + TextOutputOptions new_options = options.WithIndent("xyz"); + EXPECT_EQ("", options.current_indent()); + EXPECT_EQ("", options.indent()); + EXPECT_EQ("", new_options.current_indent()); + EXPECT_EQ("xyz", new_options.indent()); +} + +TEST(TextOutputOptions, PlusOneIndent) { + TextOutputOptions options; + TextOutputOptions new_options = options.WithIndent("xyz").PlusOneIndent(); + EXPECT_EQ("", options.current_indent()); + EXPECT_EQ("", options.indent()); + EXPECT_EQ("xyz", new_options.current_indent()); + EXPECT_EQ("xyz", new_options.indent()); + EXPECT_EQ("xyzxyz", new_options.PlusOneIndent().current_indent()); +} + +TEST(TextOutputOptions, WithComments) { + TextOutputOptions options; + TextOutputOptions new_options = options.WithComments(true); + EXPECT_FALSE(options.comments()); + EXPECT_TRUE(new_options.comments()); +} + +TEST(TextOutputOptions, WithDigitGrouping) { + TextOutputOptions options; + TextOutputOptions new_options = options.WithDigitGrouping(true); + EXPECT_FALSE(options.digit_grouping()); + EXPECT_TRUE(new_options.digit_grouping()); +} + +TEST(TextOutputOptions, Multiline) { + TextOutputOptions options; + TextOutputOptions new_options = options.Multiline(true); + EXPECT_FALSE(options.multiline()); + EXPECT_TRUE(new_options.multiline()); +} + +TEST(TextOutputOptions, WithNumericBase) { + TextOutputOptions options; + TextOutputOptions new_options = options.WithNumericBase(2); + EXPECT_EQ(10, options.numeric_base()); + EXPECT_EQ(2, new_options.numeric_base()); +} + +// Small helper function for the various WriteIntegerToTextStream tests; just +// sets up a stream, forwards its arguments to WriteIntegerToTextStream, and +// then returns the text from the stream. +template <typename Arg0, typename... Args> +::std::string WriteIntegerToString(Arg0 &&arg0, Args &&... args) { + TextOutputStream stream; + WriteIntegerToTextStream(::std::forward<Arg0>(arg0), &stream, + ::std::forward<Args>(args)...); + return stream.Result(); +} + +TEST(WriteIntegerToTextStream, Decimal) { + EXPECT_EQ("0", WriteIntegerToString(static_cast<uint8_t>(0), 10, false)); + EXPECT_EQ("100", WriteIntegerToString(static_cast<uint8_t>(100), 10, false)); + EXPECT_EQ("255", WriteIntegerToString(static_cast<uint8_t>(255), 10, false)); + EXPECT_EQ("-128", WriteIntegerToString(static_cast<int8_t>(-128), 10, false)); + EXPECT_EQ("-100", WriteIntegerToString(static_cast<int8_t>(-100), 10, false)); + EXPECT_EQ("0", WriteIntegerToString(static_cast<int8_t>(0), 10, false)); + EXPECT_EQ("100", WriteIntegerToString(static_cast<int8_t>(100), 10, false)); + EXPECT_EQ("127", WriteIntegerToString(static_cast<int8_t>(127), 10, false)); + + EXPECT_EQ("0", WriteIntegerToString(static_cast<uint8_t>(0), 10, true)); + EXPECT_EQ("100", WriteIntegerToString(static_cast<uint8_t>(100), 10, true)); + EXPECT_EQ("255", WriteIntegerToString(static_cast<uint8_t>(255), 10, true)); + EXPECT_EQ("-128", WriteIntegerToString(static_cast<int8_t>(-128), 10, true)); + EXPECT_EQ("-100", WriteIntegerToString(static_cast<int8_t>(-100), 10, true)); + EXPECT_EQ("0", WriteIntegerToString(static_cast<int8_t>(0), 10, true)); + EXPECT_EQ("100", WriteIntegerToString(static_cast<int8_t>(100), 10, true)); + EXPECT_EQ("127", WriteIntegerToString(static_cast<int8_t>(127), 10, true)); + + EXPECT_EQ("0", WriteIntegerToString(static_cast<uint16_t>(0), 10, false)); + EXPECT_EQ("1000", + WriteIntegerToString(static_cast<uint16_t>(1000), 10, false)); + EXPECT_EQ("65535", + WriteIntegerToString(static_cast<uint16_t>(65535), 10, false)); + EXPECT_EQ("-32768", + WriteIntegerToString(static_cast<int16_t>(-32768), 10, false)); + EXPECT_EQ("-10000", + WriteIntegerToString(static_cast<int16_t>(-10000), 10, false)); + EXPECT_EQ("0", WriteIntegerToString(static_cast<int16_t>(0), 10, false)); + EXPECT_EQ("32767", + WriteIntegerToString(static_cast<int16_t>(32767), 10, false)); + + EXPECT_EQ("0", WriteIntegerToString(static_cast<uint16_t>(0), 10, true)); + EXPECT_EQ("999", WriteIntegerToString(static_cast<uint16_t>(999), 10, true)); + EXPECT_EQ("1_000", + WriteIntegerToString(static_cast<uint16_t>(1000), 10, true)); + EXPECT_EQ("65_535", + WriteIntegerToString(static_cast<uint16_t>(65535), 10, true)); + EXPECT_EQ("-32_768", + WriteIntegerToString(static_cast<int16_t>(-32768), 10, true)); + EXPECT_EQ("-1_000", + WriteIntegerToString(static_cast<int16_t>(-1000), 10, true)); + EXPECT_EQ("-999", WriteIntegerToString(static_cast<int16_t>(-999), 10, true)); + EXPECT_EQ("0", WriteIntegerToString(static_cast<int16_t>(0), 10, true)); + EXPECT_EQ("32_767", + WriteIntegerToString(static_cast<int16_t>(32767), 10, true)); + + EXPECT_EQ("0", WriteIntegerToString(static_cast<uint32_t>(0), 10, false)); + EXPECT_EQ("1000000", + WriteIntegerToString(static_cast<uint32_t>(1000000), 10, false)); + EXPECT_EQ("4294967295", + WriteIntegerToString(static_cast<uint32_t>(4294967295), 10, false)); + EXPECT_EQ("-2147483648", + WriteIntegerToString(static_cast<int32_t>(-2147483648), 10, false)); + EXPECT_EQ("-100000", + WriteIntegerToString(static_cast<int32_t>(-100000), 10, false)); + EXPECT_EQ("0", WriteIntegerToString(static_cast<int32_t>(0), 10, false)); + EXPECT_EQ("2147483647", + WriteIntegerToString(static_cast<int32_t>(2147483647), 10, false)); + + EXPECT_EQ("0", WriteIntegerToString(static_cast<uint32_t>(0), 10, true)); + EXPECT_EQ("999_999", + WriteIntegerToString(static_cast<uint32_t>(999999), 10, true)); + EXPECT_EQ("1_000_000", + WriteIntegerToString(static_cast<uint32_t>(1000000), 10, true)); + EXPECT_EQ("4_294_967_295", + WriteIntegerToString(static_cast<uint32_t>(4294967295U), 10, true)); + EXPECT_EQ("-2_147_483_648", + WriteIntegerToString(static_cast<int32_t>(-2147483648L), 10, true)); + EXPECT_EQ("-999_999", + WriteIntegerToString(static_cast<int32_t>(-999999), 10, true)); + EXPECT_EQ("-1_000_000", + WriteIntegerToString(static_cast<int32_t>(-1000000), 10, true)); + EXPECT_EQ("0", WriteIntegerToString(static_cast<int32_t>(0), 10, true)); + EXPECT_EQ("2_147_483_647", + WriteIntegerToString(static_cast<int32_t>(2147483647), 10, true)); + + EXPECT_EQ("0", WriteIntegerToString(static_cast<uint64_t>(0), 10, false)); + EXPECT_EQ("1000000", + WriteIntegerToString(static_cast<uint64_t>(1000000), 10, false)); + EXPECT_EQ("18446744073709551615", + WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL), + 10, false)); + EXPECT_EQ("-9223372036854775808", + WriteIntegerToString( + static_cast<int64_t>(-9223372036854775807L - 1), 10, false)); + EXPECT_EQ("-100000", + WriteIntegerToString(static_cast<int64_t>(-100000), 10, false)); + EXPECT_EQ("0", WriteIntegerToString(static_cast<int64_t>(0), 10, false)); + EXPECT_EQ("9223372036854775807", + WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 10, + false)); + + EXPECT_EQ("0", WriteIntegerToString(static_cast<uint64_t>(0), 10, true)); + EXPECT_EQ("1_000_000", + WriteIntegerToString(static_cast<uint64_t>(1000000), 10, true)); + EXPECT_EQ("18_446_744_073_709_551_615", + WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL), + 10, true)); + EXPECT_EQ("-9_223_372_036_854_775_808", + WriteIntegerToString( + static_cast<int64_t>(-9223372036854775807L - 1), 10, true)); + EXPECT_EQ("-100_000", + WriteIntegerToString(static_cast<int64_t>(-100000), 10, true)); + EXPECT_EQ("0", WriteIntegerToString(static_cast<int64_t>(0), 10, true)); + EXPECT_EQ("9_223_372_036_854_775_807", + WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 10, + true)); +} + +TEST(WriteIntegerToTextStream, Binary) { + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint8_t>(0), 2, false)); + EXPECT_EQ("0b1100100", + WriteIntegerToString(static_cast<uint8_t>(100), 2, false)); + EXPECT_EQ("0b11111111", + WriteIntegerToString(static_cast<uint8_t>(255), 2, false)); + EXPECT_EQ("-0b10000000", + WriteIntegerToString(static_cast<int8_t>(-128), 2, false)); + EXPECT_EQ("-0b1100100", + WriteIntegerToString(static_cast<int8_t>(-100), 2, false)); + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int8_t>(0), 2, false)); + EXPECT_EQ("0b1100100", + WriteIntegerToString(static_cast<int8_t>(100), 2, false)); + EXPECT_EQ("0b1111111", + WriteIntegerToString(static_cast<int8_t>(127), 2, false)); + + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint8_t>(0), 2, true)); + EXPECT_EQ("0b1100100", + WriteIntegerToString(static_cast<uint8_t>(100), 2, true)); + EXPECT_EQ("0b11111111", + WriteIntegerToString(static_cast<uint8_t>(255), 2, true)); + EXPECT_EQ("-0b10000000", + WriteIntegerToString(static_cast<int8_t>(-128), 2, true)); + EXPECT_EQ("-0b1100100", + WriteIntegerToString(static_cast<int8_t>(-100), 2, true)); + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int8_t>(0), 2, true)); + EXPECT_EQ("0b1100100", + WriteIntegerToString(static_cast<int8_t>(100), 2, true)); + EXPECT_EQ("0b1111111", + WriteIntegerToString(static_cast<int8_t>(127), 2, true)); + + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint16_t>(0), 2, false)); + EXPECT_EQ("0b1111101000", + WriteIntegerToString(static_cast<uint16_t>(1000), 2, false)); + EXPECT_EQ("0b1111111111111111", + WriteIntegerToString(static_cast<uint16_t>(65535), 2, false)); + EXPECT_EQ("-0b1000000000000000", + WriteIntegerToString(static_cast<int16_t>(-32768), 2, false)); + EXPECT_EQ("-0b10011100010000", + WriteIntegerToString(static_cast<int16_t>(-10000), 2, false)); + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int16_t>(0), 2, false)); + EXPECT_EQ("0b111111111111111", + WriteIntegerToString(static_cast<int16_t>(32767), 2, false)); + + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint16_t>(0), 2, true)); + EXPECT_EQ("0b11_11101000", + WriteIntegerToString(static_cast<uint16_t>(1000), 2, true)); + EXPECT_EQ("0b11111111_11111111", + WriteIntegerToString(static_cast<uint16_t>(65535), 2, true)); + EXPECT_EQ("-0b10000000_00000000", + WriteIntegerToString(static_cast<int16_t>(-32768), 2, true)); + EXPECT_EQ("-0b11_11101000", + WriteIntegerToString(static_cast<int16_t>(-1000), 2, true)); + EXPECT_EQ("-0b11_11100111", + WriteIntegerToString(static_cast<int16_t>(-999), 2, true)); + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int16_t>(0), 2, true)); + EXPECT_EQ("0b1111111_11111111", + WriteIntegerToString(static_cast<int16_t>(32767), 2, true)); + + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint32_t>(0), 2, false)); + EXPECT_EQ("0b11110100001001000000", + WriteIntegerToString(static_cast<uint32_t>(1000000), 2, false)); + EXPECT_EQ("0b11111111111111111111111111111111", + WriteIntegerToString(static_cast<uint32_t>(4294967295), 2, false)); + EXPECT_EQ("-0b10000000000000000000000000000000", + WriteIntegerToString(static_cast<int32_t>(-2147483648), 2, false)); + EXPECT_EQ("-0b11000011010100000", + WriteIntegerToString(static_cast<int32_t>(-100000), 2, false)); + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int32_t>(0), 2, false)); + EXPECT_EQ("0b1111111111111111111111111111111", + WriteIntegerToString(static_cast<int32_t>(2147483647), 2, false)); + + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint32_t>(0), 2, true)); + EXPECT_EQ("0b1111_01000010_01000000", + WriteIntegerToString(static_cast<uint32_t>(1000000), 2, true)); + EXPECT_EQ("0b11111111_11111111_11111111_11111111", + WriteIntegerToString(static_cast<uint32_t>(4294967295U), 2, true)); + EXPECT_EQ("-0b10000000_00000000_00000000_00000000", + WriteIntegerToString(static_cast<int32_t>(-2147483648L), 2, true)); + EXPECT_EQ("-0b1111_01000010_01000000", + WriteIntegerToString(static_cast<int32_t>(-1000000), 2, true)); + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int32_t>(0), 2, true)); + EXPECT_EQ("0b1111111_11111111_11111111_11111111", + WriteIntegerToString(static_cast<int32_t>(2147483647), 2, true)); + + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint64_t>(0), 2, false)); + EXPECT_EQ("0b11110100001001000000", + WriteIntegerToString(static_cast<uint64_t>(1000000), 2, false)); + EXPECT_EQ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL), 2, + false)); + EXPECT_EQ( + "-0b1000000000000000000000000000000000000000000000000000000000000000", + WriteIntegerToString(static_cast<int64_t>(-9223372036854775807L - 1), 2, + false)); + EXPECT_EQ("-0b11000011010100000", + WriteIntegerToString(static_cast<int64_t>(-100000), 2, false)); + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int64_t>(0), 2, false)); + EXPECT_EQ("0b111111111111111111111111111111111111111111111111111111111111111", + WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 2, + false)); + + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint64_t>(0), 2, true)); + EXPECT_EQ("0b1111_01000010_01000000", + WriteIntegerToString(static_cast<uint64_t>(1000000), 2, true)); + EXPECT_EQ( + "0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_" + "11111111", + WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL), 2, + true)); + EXPECT_EQ( + "-0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_" + "00000000", + WriteIntegerToString(static_cast<int64_t>(-9223372036854775807L - 1), 2, + true)); + EXPECT_EQ("-0b1_10000110_10100000", + WriteIntegerToString(static_cast<int64_t>(-100000), 2, true)); + EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int64_t>(0), 2, true)); + EXPECT_EQ( + "0b1111111_11111111_11111111_11111111_11111111_11111111_11111111_" + "11111111", + WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 2, + true)); +} + +TEST(WriteIntegerToTextStream, Hexadecimal) { + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint8_t>(0), 16, false)); + EXPECT_EQ("0x64", WriteIntegerToString(static_cast<uint8_t>(100), 16, false)); + EXPECT_EQ("0xff", WriteIntegerToString(static_cast<uint8_t>(255), 16, false)); + EXPECT_EQ("-0x80", + WriteIntegerToString(static_cast<int8_t>(-128), 16, false)); + EXPECT_EQ("-0x64", + WriteIntegerToString(static_cast<int8_t>(-100), 16, false)); + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int8_t>(0), 16, false)); + EXPECT_EQ("0x64", WriteIntegerToString(static_cast<int8_t>(100), 16, false)); + EXPECT_EQ("0x7f", WriteIntegerToString(static_cast<int8_t>(127), 16, false)); + + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint8_t>(0), 16, true)); + EXPECT_EQ("0x64", WriteIntegerToString(static_cast<uint8_t>(100), 16, true)); + EXPECT_EQ("0xff", WriteIntegerToString(static_cast<uint8_t>(255), 16, true)); + EXPECT_EQ("-0x80", WriteIntegerToString(static_cast<int8_t>(-128), 16, true)); + EXPECT_EQ("-0x64", WriteIntegerToString(static_cast<int8_t>(-100), 16, true)); + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int8_t>(0), 16, true)); + EXPECT_EQ("0x64", WriteIntegerToString(static_cast<int8_t>(100), 16, true)); + EXPECT_EQ("0x7f", WriteIntegerToString(static_cast<int8_t>(127), 16, true)); + + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint16_t>(0), 16, false)); + EXPECT_EQ("0x3e8", + WriteIntegerToString(static_cast<uint16_t>(1000), 16, false)); + EXPECT_EQ("0xffff", + WriteIntegerToString(static_cast<uint16_t>(65535), 16, false)); + EXPECT_EQ("-0x8000", + WriteIntegerToString(static_cast<int16_t>(-32768), 16, false)); + EXPECT_EQ("-0x2710", + WriteIntegerToString(static_cast<int16_t>(-10000), 16, false)); + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int16_t>(0), 16, false)); + EXPECT_EQ("0x7fff", + WriteIntegerToString(static_cast<int16_t>(32767), 16, false)); + + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint16_t>(0), 16, true)); + EXPECT_EQ("0x3e8", + WriteIntegerToString(static_cast<uint16_t>(1000), 16, true)); + EXPECT_EQ("0xffff", + WriteIntegerToString(static_cast<uint16_t>(65535), 16, true)); + EXPECT_EQ("-0x8000", + WriteIntegerToString(static_cast<int16_t>(-32768), 16, true)); + EXPECT_EQ("-0x3e8", + WriteIntegerToString(static_cast<int16_t>(-1000), 16, true)); + EXPECT_EQ("-0x3e7", + WriteIntegerToString(static_cast<int16_t>(-999), 16, true)); + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int16_t>(0), 16, true)); + EXPECT_EQ("0x7fff", + WriteIntegerToString(static_cast<int16_t>(32767), 16, true)); + + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint32_t>(0), 16, false)); + EXPECT_EQ("0xf4240", + WriteIntegerToString(static_cast<uint32_t>(1000000), 16, false)); + EXPECT_EQ("0xffffffff", + WriteIntegerToString(static_cast<uint32_t>(4294967295), 16, false)); + EXPECT_EQ("-0x80000000", + WriteIntegerToString(static_cast<int32_t>(-2147483648), 16, false)); + EXPECT_EQ("-0x186a0", + WriteIntegerToString(static_cast<int32_t>(-100000), 16, false)); + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int32_t>(0), 16, false)); + EXPECT_EQ("0x7fffffff", + WriteIntegerToString(static_cast<int32_t>(2147483647), 16, false)); + + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint32_t>(0), 16, true)); + EXPECT_EQ("0xf_4240", + WriteIntegerToString(static_cast<uint32_t>(1000000), 16, true)); + EXPECT_EQ("0xffff_ffff", + WriteIntegerToString(static_cast<uint32_t>(4294967295U), 16, true)); + EXPECT_EQ("-0x8000_0000", + WriteIntegerToString(static_cast<int32_t>(-2147483648L), 16, true)); + EXPECT_EQ("-0xf_4240", + WriteIntegerToString(static_cast<int32_t>(-1000000), 16, true)); + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int32_t>(0), 16, true)); + EXPECT_EQ("0x7fff_ffff", + WriteIntegerToString(static_cast<int32_t>(2147483647), 16, true)); + + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint64_t>(0), 16, false)); + EXPECT_EQ("0xf4240", + WriteIntegerToString(static_cast<uint64_t>(1000000), 16, false)); + EXPECT_EQ("0xffffffffffffffff", + WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL), + 16, false)); + EXPECT_EQ("-0x8000000000000000", + WriteIntegerToString( + static_cast<int64_t>(-9223372036854775807L - 1), 16, false)); + EXPECT_EQ("-0x186a0", + WriteIntegerToString(static_cast<int64_t>(-100000), 16, false)); + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int64_t>(0), 16, false)); + EXPECT_EQ("0x7fffffffffffffff", + WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 16, + false)); + + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint64_t>(0), 16, true)); + EXPECT_EQ("0xf_4240", + WriteIntegerToString(static_cast<uint64_t>(1000000), 16, true)); + EXPECT_EQ("0xffff_ffff_ffff_ffff", + WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL), + 16, true)); + EXPECT_EQ("-0x8000_0000_0000_0000", + WriteIntegerToString( + static_cast<int64_t>(-9223372036854775807L - 1), 16, true)); + EXPECT_EQ("-0x1_86a0", + WriteIntegerToString(static_cast<int64_t>(-100000), 16, true)); + EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int64_t>(0), 16, true)); + EXPECT_EQ("0x7fff_ffff_ffff_ffff", + WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 16, + true)); +} + +// Small helper function for the various WriteFloatToTextStream tests; just sets +// up a stream, forwards its arguments to WriteFloatToTextStream, and then +// returns the text from the stream. +template <typename Arg0, typename... Args> +::std::string WriteFloatToString(Arg0 &&arg0, Args &&... args) { + TextOutputStream stream; + WriteFloatToTextStream(::std::forward<Arg0>(arg0), &stream, + ::std::forward<Args>(args)...); + return stream.Result(); +} + +TEST(WriteFloatToTextStream, RegularNumbers) { + EXPECT_EQ("0", WriteFloatToString(0.0, TextOutputOptions())); + EXPECT_EQ("1", WriteFloatToString(1.0, TextOutputOptions())); + EXPECT_EQ("1.5", WriteFloatToString(1.5, TextOutputOptions())); + // TODO(bolms): Figure out how to get minimal-length output. + EXPECT_EQ("1.6000000000000001", WriteFloatToString(1.6, TextOutputOptions())); + EXPECT_EQ("123456789", WriteFloatToString(123456789.0, TextOutputOptions())); + EXPECT_EQ("12345678901234568", + WriteFloatToString(12345678901234567.0, TextOutputOptions())); + EXPECT_EQ("-12345678901234568", + WriteFloatToString(-12345678901234567.0, TextOutputOptions())); + EXPECT_EQ("-1.2345678901234568e+17", + WriteFloatToString(-123456789012345678.0, TextOutputOptions())); + EXPECT_EQ("4.9406564584124654e-324", + WriteFloatToString(::std::numeric_limits<double>::denorm_min(), + TextOutputOptions())); + EXPECT_EQ("1.7976931348623157e+308", + WriteFloatToString(::std::numeric_limits<double>::max(), + TextOutputOptions())); + + EXPECT_EQ("0", WriteFloatToString(0.0f, TextOutputOptions())); + EXPECT_EQ("1", WriteFloatToString(1.0f, TextOutputOptions())); + EXPECT_EQ("1.5", WriteFloatToString(1.5f, TextOutputOptions())); + EXPECT_EQ("1.60000002", WriteFloatToString(1.6f, TextOutputOptions())); + EXPECT_EQ("123456792", WriteFloatToString(123456789.0f, TextOutputOptions())); + EXPECT_EQ("1.23456784e+16", + WriteFloatToString(12345678901234567.0f, TextOutputOptions())); + EXPECT_EQ("-1.23456784e+16", + WriteFloatToString(-12345678901234567.0f, TextOutputOptions())); + EXPECT_EQ("-1.00000003e+16", + WriteFloatToString(-10000000000000000.0f, TextOutputOptions())); + EXPECT_EQ("1.40129846e-45", + WriteFloatToString(::std::numeric_limits<float>::denorm_min(), + TextOutputOptions())); + EXPECT_EQ("3.40282347e+38", + WriteFloatToString(::std::numeric_limits<float>::max(), + TextOutputOptions())); +} + +TEST(WriteFloatToTextStream, Infinities) { + EXPECT_EQ("Inf", WriteFloatToString(2 * ::std::numeric_limits<double>::max(), + TextOutputOptions())); + EXPECT_EQ("Inf", WriteFloatToString(2 * ::std::numeric_limits<float>::max(), + TextOutputOptions())); + EXPECT_EQ("-Inf", + WriteFloatToString(-2 * ::std::numeric_limits<double>::max(), + TextOutputOptions())); + EXPECT_EQ("-Inf", WriteFloatToString(-2 * ::std::numeric_limits<float>::max(), + TextOutputOptions())); +} + +// C++ does not provide great low-level manipulation for NaNs, so we resort to +// this mess. +double MakeNanDouble(::std::uint64_t payload, int sign) { + payload |= 0x7ff0000000000000UL; + if (sign < 0) { + payload |= 0x8000000000000000UL; + } + double result; + ::std::memcpy(&result, &payload, sizeof result); + return result; +} + +float MakeNanFloat(::std::uint32_t payload, int sign) { + payload |= 0x7f800000U; + if (sign < 0) { + payload |= 0x80000000U; + } + float result; + ::std::memcpy(&result, &payload, sizeof result); + return result; +} + +TEST(WriteFloatToTextStream, Nans) { + EXPECT_EQ("NaN(0x1)", + WriteFloatToString(MakeNanDouble(1, 0), TextOutputOptions())); + EXPECT_EQ("NaN(0x1)", + WriteFloatToString(MakeNanFloat(1, 0), TextOutputOptions())); + EXPECT_EQ("NaN(0x10000)", + WriteFloatToString(MakeNanDouble(0x10000, 0), TextOutputOptions())); + EXPECT_EQ("NaN(0x7fffff)", WriteFloatToString(MakeNanFloat(0x7fffffU, 0), + TextOutputOptions())); + EXPECT_EQ("NaN(0xfffffffffffff)", + WriteFloatToString(MakeNanDouble(0xfffffffffffffUL, 0), + TextOutputOptions())); + EXPECT_EQ("-NaN(0x7fffff)", WriteFloatToString(MakeNanFloat(0x7fffffU, -1), + TextOutputOptions())); + EXPECT_EQ("-NaN(0xfffffffffffff)", + WriteFloatToString(MakeNanDouble(0xfffffffffffffUL, -1), + TextOutputOptions())); + EXPECT_EQ("NaN(0x10000)", + WriteFloatToString(MakeNanFloat(0x10000, 0), TextOutputOptions())); + EXPECT_EQ("-NaN(0x1)", + WriteFloatToString(MakeNanDouble(1, -1), TextOutputOptions())); + EXPECT_EQ("-NaN(0x1)", + WriteFloatToString(MakeNanFloat(1, -1), TextOutputOptions())); + EXPECT_EQ("-NaN(0x10000)", WriteFloatToString(MakeNanDouble(0x10000, -1), + TextOutputOptions())); + EXPECT_EQ("-NaN(0x10000)", + WriteFloatToString(MakeNanFloat(0x10000, -1), TextOutputOptions())); + EXPECT_EQ("-NaN(0x1_0000)", + WriteFloatToString(MakeNanDouble(0x10000, -1), + TextOutputOptions().WithDigitGrouping(true))); + EXPECT_EQ("-NaN(0x1_0000)", + WriteFloatToString(MakeNanFloat(0x10000, -1), + TextOutputOptions().WithDigitGrouping(true))); +} + +TEST(DecodeFloat, RegularNumbers) { + double double_result; + EXPECT_TRUE(DecodeFloat("0", &double_result)); + EXPECT_EQ(0.0, double_result); + EXPECT_FALSE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("-0", &double_result)); + EXPECT_EQ(0.0, double_result); + EXPECT_TRUE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("0.0", &double_result)); + EXPECT_EQ(0.0, double_result); + EXPECT_TRUE(DecodeFloat("0.0e100", &double_result)); + EXPECT_EQ(0.0, double_result); + EXPECT_TRUE(DecodeFloat("0x0.0p100", &double_result)); + EXPECT_EQ(0.0, double_result); + EXPECT_TRUE(DecodeFloat("1", &double_result)); + EXPECT_EQ(1.0, double_result); + EXPECT_TRUE(DecodeFloat("1.5", &double_result)); + EXPECT_EQ(1.5, double_result); + EXPECT_TRUE(DecodeFloat("1.6", &double_result)); + EXPECT_EQ(1.6, double_result); + EXPECT_TRUE(DecodeFloat("1.6000000000000001", &double_result)); + EXPECT_EQ(1.6, double_result); + EXPECT_TRUE(DecodeFloat("123456789", &double_result)); + EXPECT_EQ(123456789.0, double_result); + EXPECT_TRUE(DecodeFloat("-1.234567890123458e+17", &double_result)); + EXPECT_EQ(-1.234567890123458e+17, double_result); + EXPECT_TRUE(DecodeFloat("4.9406564584124654e-324", &double_result)); + EXPECT_EQ(4.9406564584124654e-324, double_result); + EXPECT_TRUE(DecodeFloat("1.7976931348623157e+308", &double_result)); + EXPECT_EQ(1.7976931348623157e+308, double_result); + EXPECT_TRUE(DecodeFloat( + "000000000000000000000000000004.9406564584124654e-324", &double_result)); + EXPECT_EQ(4.9406564584124654e-324, double_result); + + float float_result; + EXPECT_TRUE(DecodeFloat("0", &float_result)); + EXPECT_EQ(0.0f, float_result); + EXPECT_FALSE(::std::signbit(float_result)); + EXPECT_TRUE(DecodeFloat("-0", &float_result)); + EXPECT_EQ(0.0f, float_result); + EXPECT_TRUE(::std::signbit(float_result)); + EXPECT_TRUE(DecodeFloat("0.0", &float_result)); + EXPECT_EQ(0.0f, float_result); + EXPECT_TRUE(DecodeFloat("0.0e100", &float_result)); + EXPECT_EQ(0.0f, float_result); + EXPECT_TRUE(DecodeFloat("0x0.0p100", &float_result)); + EXPECT_EQ(0.0f, float_result); + EXPECT_TRUE(DecodeFloat("1", &float_result)); + EXPECT_EQ(1.0f, float_result); + EXPECT_TRUE(DecodeFloat("1.5", &float_result)); + EXPECT_EQ(1.5f, float_result); + EXPECT_TRUE(DecodeFloat("1.6", &float_result)); + EXPECT_EQ(1.6f, float_result); + EXPECT_TRUE(DecodeFloat("1.6000000000000001", &float_result)); + EXPECT_EQ(1.6f, float_result); + EXPECT_TRUE(DecodeFloat("123456789", &float_result)); + EXPECT_EQ(123456789.0f, float_result); + EXPECT_TRUE(DecodeFloat("-1.23456784e+16", &float_result)); + EXPECT_EQ(-1.23456784e+16f, float_result); + EXPECT_TRUE(DecodeFloat("1.40129846e-45", &float_result)); + EXPECT_EQ(1.40129846e-45f, float_result); + EXPECT_TRUE(DecodeFloat("3.40282347e+38", &float_result)); + EXPECT_EQ(3.40282347e+38f, float_result); + + // TODO(bolms): "_"-grouped numbers, like "123_456.789", should probably be + // allowed. +} + +TEST(DecodeFloat, BadValues) { + double result; + float float_result; + + // No text is not a value. + EXPECT_FALSE(DecodeFloat("", &result)); + + // Trailing characters after "Inf" are not allowed. + EXPECT_FALSE(DecodeFloat("INF+", &result)); + EXPECT_FALSE(DecodeFloat("Infinity", &result)); + + // Trailing characters after "NaN" are not allowed. + EXPECT_FALSE(DecodeFloat("NaN(", &result)); + EXPECT_FALSE(DecodeFloat("NaN(0]", &result)); + EXPECT_FALSE(DecodeFloat("NaNaNaNa", &result)); + + // Non-number NaN payloads are not allowed. + EXPECT_FALSE(DecodeFloat("NaN()", &result)); + EXPECT_FALSE(DecodeFloat("NaN(x)", &result)); + EXPECT_FALSE(DecodeFloat("NaN(0x)", &result)); + + // Negative NaN payloads are not allowed. + EXPECT_FALSE(DecodeFloat("NaN(-1)", &result)); + EXPECT_FALSE(DecodeFloat("NaN(-0)", &result)); + + // NaN with zero payload is infinity, and is thus not allowed. + EXPECT_FALSE(DecodeFloat("NaN(0)", &result)); + EXPECT_FALSE(DecodeFloat("-NaN(0)", &result)); + + // NaN double payloads must be no more than 52 bits. + EXPECT_FALSE(DecodeFloat("NaN(0x10_0000_0000_0000)", &result)); + EXPECT_FALSE(DecodeFloat("NaN(0x8000_0000_0000_0000)", &result)); + EXPECT_FALSE(DecodeFloat("NaN(0x1_0000_0000_0000_0000)", &result)); + + // NaN float payloads must be no more than 23 bits. + EXPECT_FALSE(DecodeFloat("NaN(0x80_0000)", &float_result)); + EXPECT_FALSE(DecodeFloat("NaN(0x8000_0000)", &float_result)); + EXPECT_FALSE(DecodeFloat("NaN(0x1_0000_0000)", &float_result)); + + // Trailing characters after regular values are not allowed. + EXPECT_FALSE(DecodeFloat("0x", &result)); + EXPECT_FALSE(DecodeFloat("0e0a", &result)); + EXPECT_FALSE(DecodeFloat("0b0", &result)); + EXPECT_FALSE(DecodeFloat("0a", &result)); + EXPECT_FALSE(DecodeFloat("1..", &result)); + + // Grouping characters like "," should not be allowed. + EXPECT_FALSE(DecodeFloat("123,456", &result)); + EXPECT_FALSE(DecodeFloat("123'456", &result)); +} + +TEST(DecodeFloat, Infinities) { + double double_result; + EXPECT_TRUE(DecodeFloat("Inf", &double_result)); + EXPECT_TRUE(::std::isinf(double_result)); + EXPECT_FALSE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("-Inf", &double_result)); + EXPECT_TRUE(::std::isinf(double_result)); + EXPECT_TRUE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("+Inf", &double_result)); + EXPECT_TRUE(::std::isinf(double_result)); + EXPECT_FALSE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("iNF", &double_result)); + EXPECT_TRUE(::std::isinf(double_result)); + EXPECT_FALSE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("-iNF", &double_result)); + EXPECT_TRUE(::std::isinf(double_result)); + EXPECT_TRUE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("+iNF", &double_result)); + EXPECT_TRUE(::std::isinf(double_result)); + EXPECT_FALSE(::std::signbit(double_result)); +} + +// Helper functions for converting NaNs to bit patterns, so that the exact bit +// pattern result can be tested. +::std::uint64_t DoubleBitPattern(double n) { + ::std::uint64_t result; + memcpy(&result, &n, sizeof(result)); + return result; +} + +::std::uint32_t FloatBitPattern(float n) { + ::std::uint32_t result; + memcpy(&result, &n, sizeof(result)); + return result; +} + +TEST(DecodeFloat, Nans) { + double double_result; + EXPECT_TRUE(DecodeFloat("nan", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_FALSE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("-NAN", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_TRUE(::std::signbit(double_result)); + EXPECT_TRUE(DecodeFloat("NaN(1)", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_EQ(0x7ff0000000000001UL, DoubleBitPattern(double_result)); + EXPECT_TRUE(DecodeFloat("nAn(0x1000)", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_EQ(0x7ff0000000001000UL, DoubleBitPattern(double_result)); + EXPECT_TRUE(DecodeFloat("NaN(0b11000011)", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_EQ(0x7ff00000000000c3UL, DoubleBitPattern(double_result)); + EXPECT_TRUE(DecodeFloat("-NaN(0b11000011)", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_EQ(0xfff00000000000c3UL, DoubleBitPattern(double_result)); + EXPECT_TRUE(DecodeFloat("+NaN(0b11000011)", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_EQ(0x7ff00000000000c3UL, DoubleBitPattern(double_result)); + EXPECT_TRUE(DecodeFloat("NaN(0xf_ffff_ffff_ffff)", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_EQ(0x7fffffffffffffffUL, DoubleBitPattern(double_result)); + EXPECT_TRUE(DecodeFloat("-NaN(0xf_ffff_ffff_ffff)", &double_result)); + EXPECT_TRUE(::std::isnan(double_result)); + EXPECT_EQ(0xffffffffffffffffUL, DoubleBitPattern(double_result)); + + float float_result; + EXPECT_TRUE(DecodeFloat("nan", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_FALSE(::std::signbit(float_result)); + EXPECT_TRUE(DecodeFloat("-NAN", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_TRUE(::std::signbit(float_result)); + EXPECT_TRUE(DecodeFloat("NaN(1)", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_EQ(0x7f800001U, FloatBitPattern(float_result)); + EXPECT_TRUE(DecodeFloat("nAn(0x1000)", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_EQ(0x7f801000U, FloatBitPattern(float_result)); + EXPECT_TRUE(DecodeFloat("NaN(0b11000011)", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_EQ(0x7f8000c3U, FloatBitPattern(float_result)); + EXPECT_TRUE(DecodeFloat("-NaN(0b11000011)", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_EQ(0xff8000c3U, FloatBitPattern(float_result)); + EXPECT_TRUE(DecodeFloat("+NaN(0b11000011)", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_EQ(0x7f8000c3U, FloatBitPattern(float_result)); + EXPECT_TRUE(DecodeFloat("NaN(0x7f_ffff)", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_EQ(0x7fffffffU, FloatBitPattern(float_result)); + EXPECT_TRUE(DecodeFloat("-NaN(0x7f_ffff)", &float_result)); + EXPECT_TRUE(::std::isnan(float_result)); + EXPECT_EQ(0xffffffffU, FloatBitPattern(float_result)); +} + +} // namespace test +} // namespace support +} // namespace emboss
diff --git a/public/emboss_view_parameters.h b/public/emboss_view_parameters.h new file mode 100644 index 0000000..f2a6e06 --- /dev/null +++ b/public/emboss_view_parameters.h
@@ -0,0 +1,45 @@ +// 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. + +// Helper classes for constructing the `Parameters` template argument to view +// classes. + +#ifndef EMBOSS_PUBLIC_EMBOSS_VIEW_PARAMETERS_H_ +#define EMBOSS_PUBLIC_EMBOSS_VIEW_PARAMETERS_H_ + +namespace emboss { +namespace support { + +template <int kBitsParam, typename Verifier> +struct FixedSizeViewParameters { + static constexpr int kBits = kBitsParam; + template <typename ValueType> + static constexpr bool ValueIsOk(ValueType value) { + return Verifier::ValueIsOk(value); + } + // TODO(bolms): add AllValuesAreOk(), and use it to shortcut Ok() processing + // for arrays and other compound objects. +}; + +struct AllValuesAreOk { + template <typename ValueType> + static constexpr bool ValueIsOk(ValueType) { + return true; + } +}; + +} // namespace support +} // namespace emboss + +#endif // EMBOSS_PUBLIC_EMBOSS_VIEW_PARAMETERS_H_
diff --git a/public/ir_pb2.py b/public/ir_pb2.py new file mode 100644 index 0000000..b33a06b --- /dev/null +++ b/public/ir_pb2.py
@@ -0,0 +1,937 @@ +# 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. + +# Intermediate representation (IR) for Emboss. +# +# This was originally a Google Protocol Buffer file, but as of 2019 it turns +# out that a) the public Google Python Protocol Buffer implementation is +# extremely slow, and b) all the ways of getting Bazel+Python+Protocol Buffers +# to play nice are hacky and fragile. +# +# Thus, this file, which presents a similar-enough interface that the rest of +# Emboss can use it with minimal changes. +# +# Protobufs have a really, really strange, un-Pythonic interface, with tricky +# implicit semantics -- mostly around magically instantiating protos when you +# assign to some deeply-nested field. I (bolms@) would *strongly* prefer to +# have a more explicit interface, but don't (currently) have time to refactor +# everything that touches the IR (i.e., the entire compiler). + +import json +import re +import sys + + +if sys.version_info[0] == 2: + _text = unicode + _text_types = (unicode, str) + _int = long + _int_types = (int, long) +else: + _text = str + _text_types = (str,) + _int = int + _int_types = (int,) + + +_BASIC_TYPES = _text_types + _int_types + (bool,) + + +class Optional(object): + def __init__(self, type, oneof=None, names=None): + self._type = type + self._oneof = oneof + self._names = names + + def __get__(self, obj, type=None): + result = obj.raw_fields.get(self.name, None) + if result is not None: + return result + if self.type in _BASIC_TYPES: + return self._type() + result = self._type() + + def on_write(): + self._set_value(obj, result) + + result.set_on_write(on_write) + return result + + def __set__(self, obj, value): + if issubclass(self._type, _BASIC_TYPES): + self.set(obj, value) + else: + raise AttributeError("Cannot set {} (type {}) for type {}".format( + value, value.__class__, self._type)) + + def _set_value(self, obj, value): + if self._oneof is not None: + current = obj.oneofs.get(self._oneof) + if current in obj.raw_fields: + del obj.raw_fields[current] + obj.oneofs[self._oneof] = self.name + obj.raw_fields[self.name] = value + obj.on_write() + + def set(self, obj, value): + if value is None: + return + if isinstance(value, dict): + self._set_value(obj, self._type(**value)) + elif isinstance(value, _text) and self._names: + self._set_value(obj, self._type(self._names(value))) + elif (not isinstance(value, self._type) and + not (self._type == _int and isinstance(value, _int_types)) and + not (self._type == _text and isinstance(value, _text_types))): + raise AttributeError("Cannot set {} (type {}) for type {}".format( + value, value.__class__, self._type)) + elif issubclass(self._type, Message): + self._set_value(obj, self._type(**value.raw_fields)) + else: + self._set_value(obj, self._type(value)) + + def resolve_type(self): + if isinstance(self._type, type(lambda: None)): + self._type = self._type() + + @property + def type(self): + return self._type + + +class Oneof(object): + def __init__(self, **members): + self.members = members + + def __get__(self, obj, type=None): + return obj.oneofs[self.name] + + def __set__(self, obj, value): + raise AttributeError("Cannot set {}".format(self.name)) + + +class TypedScopedList(object): + def __init__(self, type, on_write=lambda: None): + self._type = type + self._list = [] + self._on_write = on_write + + def __iter__(self): + return iter(self._list) + + def __delitem__(self, key): + del self._list[key] + + def __getitem__(self, key): + return self._list[key] + + def extend(self, values): + for value in values: + if isinstance(value, dict): + self._list.append(self._type(**value)) + elif (not isinstance(value, self._type) and + not (self._type == _int and isinstance(value, _int_types)) and + not (self._type == _text and isinstance(value, _text_types))): + raise TypeError( + "Needed {}, got {} ({!r})".format( + self._type, value.__class__, value)) + else: + if self._type in _BASIC_TYPES: + self._list.append(self._type(value)) + else: + self._list.append(self._type(**value.raw_fields)) + self._on_write() + + def __repr__(self): + return repr(self._list) + + def __len__(self): + return len(self._list) + + def __eq__(self, other): + return ((self.__class__ == other.__class__ and + self._list == other._list) or + (isinstance(other, list) and self._list == other)) + + def __ne__(self, other): + return not (self == other) + + +class Repeated(object): + def __init__(self, type): + self._type = type + + def __get__(self, obj, type=None): + return obj.raw_fields[self.name] + + def __set__(self, obj, value): + raise AttributeError("Cannot set {}".format(self.name)) + + def set(self, obj, values): + typed_list = obj.raw_fields[self.name] + if not isinstance(values, (list, TypedScopedList)): + raise TypeError("Cannot initialize repeated field {} from {}".format( + self.name, values.__class__)) + del typed_list[:] + typed_list.extend(values) + + def resolve_type(self): + if isinstance(self._type, type(lambda: None)): + self._type = self._type() + + @property + def type(self): + return self._type + + +_deferred_specs = [] + +def message(cls): + # TODO(bolms): move this into __init_subclass__ after dropping Python 2 + # support. + _deferred_specs.append(cls) + return cls + +class Message(object): + + def __init__(self, **field_values): + self.oneofs = {} + self._on_write = lambda: None + self._initialize_raw_fields_from(field_values) + + def _initialize_raw_fields_from(self, field_values): + self.raw_fields = {} + for name, type in self.repeated_fields.items(): + self.raw_fields[name] = TypedScopedList(type, self.on_write) + for k, v in field_values.items(): + spec = self.field_specs.get(k) + if spec is None: + raise AttributeError("No field {} on {}.".format( + k, self.__class__.__name__)) + spec.set(self, v) + + @classmethod + def from_json(cls, text): + as_dict = json.loads(text) + return cls(**as_dict) + + def on_write(self): + self._on_write() + self._on_write = lambda: None + + def set_on_write(self, on_write): + self._on_write = on_write + + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self.raw_fields == other.raw_fields) + + def CopyFrom(self, other): + if self.__class__ != other.__class__: + raise TypeError("{} cannot CopyFrom {}".format( + self.__class__.__name__, other.__class__.__name__)) + self._initialize_raw_fields_from(other.raw_fields) + self.on_write() + + def HasField(self, name): + return name in self.raw_fields + + def WhichOneof(self, oneof_name): + return self.oneofs.get(oneof_name) + + def to_dict(self): + result = {} + for k, v in self.raw_fields.items(): + if isinstance(v, _BASIC_TYPES): + result[k] = v + elif isinstance(v, TypedScopedList): + if len(v): + # For compatibility with the proto world, empty lists are just + # elided. + result[k] = [ + item if isinstance(item, _BASIC_TYPES) else item.to_dict() + for item in v + ] + else: + result[k] = v.to_dict() + return result + + def __repr__(self): + return self.to_json(separators=(',', ':'), sort_keys=True) + + def to_json(self, *args, **kwargs): + return json.dumps(self.to_dict(), *args, **kwargs) + + def __str__(self): + return _text(self.to_dict()) + + +def _initialize_deferred_specs(): + for cls in _deferred_specs: + field_specs = {} + repeated_fields = {} + for k, v in cls.__dict__.items(): + if k[0] == "_": + continue + if isinstance(v, (Optional, Repeated)): + v.name = k + v.resolve_type() + field_specs[k] = v + if isinstance(v, Repeated): + repeated_fields[k] = v.type + cls.field_specs = field_specs + cls.repeated_fields = repeated_fields + + +################################################################################ + + +@message +class Position(Message): + """A zero-width position within a source file.""" + line = Optional(int) # Line (starts from 1). + column = Optional(int) # Column (starts from 1). + + +@message +class Location(Message): + """A half-open start:end range within a source file.""" + start = Optional(Position) # Beginning of the range. + end = Optional(Position) # One column past the end of the range. + + # True if this Location is outside of the parent object's Location. + is_disjoint_from_parent = Optional(bool) + + # True if this Location's parent was synthesized, and does not directly + # appear in the source file. The Emboss front end uses this field to cull + # irrelevant error messages. + is_synthetic = Optional(bool) + + +@message +class Word(Message): + """IR for a bare word in the source file. + + This is used in NameDefinitions and References.""" + + text = Optional(_text) + source_location = Optional(Location) + + +@message +class String(Message): + """IR for a string in the source file.""" + text = Optional(_text) + source_location = Optional(Location) + + +@message +class Documentation(Message): + text = Optional(_text) + source_location = Optional(Location) + + +@message +class BooleanConstant(Message): + """IR for a boolean constant.""" + value = Optional(bool) + source_location = Optional(Location) + + +@message +class Empty(Message): + """Placeholder message for automatic element counts for arrays.""" + source_location = Optional(Location) + + +@message +class NumericConstant(Message): + """IR for any numeric constant.""" + + # Numeric constants are stored as decimal strings; this is the simplest way + # to store the full -2**63..+2**64 range. + # + # TODO(bolms): switch back to int, and just use strings during + # serialization, now that we're free of proto. + value = Optional(_text) + source_location = Optional(Location) + + +@message +class Function(Message): + """IR for a single function (+, -, *, ==, $max, etc.) in an expression.""" + UNKNOWN = 0 + ADDITION = 1 # + + SUBTRACTION = 2 # - + MULTIPLICATION = 3 # * + EQUALITY = 4 # == + INEQUALITY = 5 # != + AND = 6 # && + OR = 7 # || + LESS = 8 # < + LESS_OR_EQUAL = 9 # <= + GREATER = 10 # > + GREATER_OR_EQUAL = 11 # >= + CHOICE = 12 # ?: + MAXIMUM = 13 # $max() + PRESENCE = 14 # $present() + UPPER_BOUND = 15 # $upper_bound() + LOWER_BOUND = 16 # $lower_bound() + + function = Optional(int, names=lambda x: getattr(Function, x)) + args = Repeated(lambda: Expression) + function_name = Optional(Word) + source_location = Optional(Location) + + +@message +class CanonicalName(Message): + """CanonicalName is the unique, absolute name for some object. + + A CanonicalName is the unique, absolute name for some object (Type, field, + etc.) in the IR. It is used both in the definitions of objects ("struct + Foo"), and in references to objects (a field of type "Foo").""" + + # The module_file is the Module.source_file_name of the Module in which this + # object's definition appears. Note that the Prelude always has a + # Module.source_file_name of "", and thus references to Prelude names will + # have module_file == "". + module_file = Optional(_text) + + # The object_path is the canonical path to the object definition within its + # module file. For example, the field "bar" would have an object path of + # ["Foo", "bar"]: + # + # struct Foo: + # 0:3 UInt bar + # + # + # The enumerated name "BOB" would have an object path of ["Baz", "Qux", + # "BOB"]: + # + # struct Baz: + # 0:3 Qux qux + # + # enum Qux: + # BOB = 0 + object_path = Repeated(_text) + + +@message +class NameDefinition(Message): + """NameDefinition is IR for the name of an object, within the object. + + That is, a TypeDefinition or Field will hold a NameDefinition as its + name.""" + + # The name, as directly generated from the source text. name.text will + # match the last element of canonical_name.object_path. Note that in some + # cases, the exact string in name.text may not appear in the source text. + name = Optional(Word) + + # The CanonicalName that will appear in References. This field is + # technically redundant: canonical_name.module_file should always match the + # source_file_name of the enclosing Module, and canonical_name.object_path + # should always match the names of parent nodes. + canonical_name = Optional(CanonicalName) + + # If true, indicates that this is an automatically-generated name, which + # should not be visible outside of its immediate namespace. + is_anonymous = Optional(bool) + + # The location of this NameDefinition in source code. + source_location = Optional(Location) + + +@message +class Reference(Message): + """A Reference holds the canonical name of something defined elsewhere. + + For example, take this fragment: + + struct Foo: + 0:3 UInt size (s) + 4:s Int:8[] payload + + "Foo", "size", and "payload" will become NameDefinitions in their + corresponding Field and Message IR objects, while "UInt", the second "s", + and "Int" are References. Note that the second "s" will have a + canonical_name.object_path of ["Foo", "size"], not ["Foo", "s"]: the + Reference always holds the single "true" name of the object, regardless of + what appears in the .emb.""" + + # The canonical name of the object being referred to. This name should be + # used to find the object in the IR. + canonical_name = Optional(CanonicalName) + + # The source_name is the name the user entered in the source file; it could + # be either relative or absolute, and may be an alias (and thus not match + # any part of the canonical_name). Back ends should use canonical_name for + # name lookup, and reserve source_name for error messages. + source_name = Repeated(Word) + + # If true, then symbol resolution should only look at local names when + # resolving source_name. This is used so that the names of inline types + # aren't "ambiguous" if there happens to be another type with the same name + # at a parent scope. + is_local_name = Optional(bool) + + # TODO(bolms): Allow absolute paths starting with ".". + + # Note that this is the source_location of the *Reference*, not of the + # object to which it refers. + source_location = Optional(Location) + + +@message +class FieldReference(Message): + """IR for a "field" or "field.sub.subsub" reference in an expression. + + The first element of "path" is the "base" field, which should be directly + readable in the (runtime) context of the expression. For example: + + struct Foo: + 0:1 UInt header_size (h) + 0:h UInt:8[] header_bytes + + The "h" will translate to ["Foo", "header_size"], which will be the first + (and in this case only) element of "path". + + Subsequent path elements should be treated as subfields. For example, in: + + struct Foo: + struct Sizes: + 0:1 UInt header_size + 1:2 UInt body_size + 0 [+2] Sizes sizes + 0 [+sizes.header_size] UInt:8[] header + sizes.header_size [+sizes.body_size] UInt:8[] body + + The references to "sizes.header_size" will have a path of [["Foo", + "sizes"], ["Foo", "Sizes", "header_size"]]. Note that each path element is + a fully-qualified reference; some back ends (C++, Python) may only use the + last element, while others (C) may use the complete path. + + This representation is a bit awkward, and is fundamentally limited to a + dotted list of static field names. It does not allow an expression like + `array[n]` on the left side of a `.`. At this point, it is an artifact of + the era during which I (bolms@) thought I could get away with skipping + compiler-y things.""" + + # TODO(bolms): Add composite types to the expression type system, and + # replace FieldReference with a "member access" Expression kind. Further, + # move the symbol resolution for FieldReferences that is currently in + # symbol_resolver.py into type_check.py. + + # TODO(bolms): Make the above change before declaring the IR to be "stable". + + path = Repeated(Reference) + source_location = Optional(Location) + + +@message +class OpaqueType(Message): + pass + + +@message +class IntegerType(Message): + """Type of an integer expression.""" + + # For optimization, the modular congruence of an integer expression is + # tracked. This consists of a modulus and a modular_value, such that for + # all possible values of expression, expression MOD modulus == + # modular_value. + # + # The modulus may be the special value "infinity" to indicate that the + # expression's value is exactly modular_value; otherwise, it should be a + # positive integer. + # + # A modulus of 1 places no constraints on the value. + # + # The modular_value should always be a nonnegative integer that is smaller + # than the modulus. + # + # Note that this is specifically the *modulus*, which is not equivalent to + # the value from C's '%' operator when the dividend is negative: in C, -7 % + # 4 == -3, but the modular_value here would be 1. Python uses modulus: in + # Python, -7 % 4 == 1. + modulus = Optional(_text) + modular_value = Optional(_text) + + # The minimum and maximum values of an integer are tracked and checked so + # that Emboss can implement reliable arithmetic with no operations + # overflowing either 64-bit unsigned or 64-bit signed 2's-complement + # integers. + # + # Note that constant subexpressions are allowed to overflow, as long as the + # final, computed constant value of the subexpression fits in a 64-bit + # value. + # + # The minimum_value may take the value "-infinity", and the maximum_value + # may take the value "infinity". These sentinel values indicate that + # Emboss has no bound information for the Expression, and therefore the + # Expression may only be evaluated during compilation; the back end should + # never need to compile such an expression into the target language (e.g., + # C++). + minimum_value = Optional(_text) + maximum_value = Optional(_text) + + +@message +class BooleanType(Message): + value = Optional(bool) + + +@message +class EnumType(Message): + name = Optional(Reference) + value = Optional(_text) + + +@message +class ExpressionType(Message): + opaque = Optional(OpaqueType, "type") + integer = Optional(IntegerType, "type") + boolean = Optional(BooleanType, "type") + enumeration = Optional(EnumType, "type") + + +@message +class Expression(Message): + """ IR for an expression. + + An Expression is a potentially-recursive data structure. It can either + represent a leaf node (constant or reference) or an operation combining + other Expressions (function).""" + + constant = Optional(NumericConstant, "expression") + constant_reference = Optional(Reference, "expression") + function = Optional(Function, "expression") + field_reference = Optional(FieldReference, "expression") + boolean_constant = Optional(BooleanConstant, "expression") + builtin_reference = Optional(Reference, "expression") + + type = Optional(ExpressionType) + source_location = Optional(Location) + + +@message +class ArrayType(Message): + """IR for an array type ("Int:8[12]" or "Message[2]" or "UInt[3][2]").""" + base_type = Optional(lambda: Type) + + element_count = Optional(Expression, "size") + automatic = Optional(Empty, "size") + + source_location = Optional(Location) + + +@message +class AtomicType(Message): + """IR for a non-array type ("UInt" or "Foo(Version.SIX)").""" + reference = Optional(Reference) + runtime_parameter = Repeated(Expression) + source_location = Optional(Location) + + +@message +class Type(Message): + """IR for a type reference ("UInt", "Int:8[12]", etc.).""" + atomic_type = Optional(AtomicType, "type") + array_type = Optional(ArrayType, "type") + + size_in_bits = Optional(Expression) + source_location = Optional(Location) + + +@message +class AttributeValue(Message): + """IR for a attribute value.""" + # TODO(bolms): Make String a type of Expression, and replace + # AttributeValue with Expression. + expression = Optional(Expression, "value") + string_constant = Optional(String, "value") + + source_location = Optional(Location) + + +@message +class Attribute(Message): + """IR for a [name = value] attribute.""" + name = Optional(Word) + value = Optional(AttributeValue) + back_end = Optional(Word) + is_default = Optional(bool) + source_location = Optional(Location) + + +@message +class WriteTransform(Message): + """IR which defines an expression-based virtual field write scheme. + + E.g., for a virtual field like `x_plus_one`: + + struct Foo: + 0 [+1] UInt x + let x_plus_one = x + 1 + + ... the `WriteMethod` would be `transform`, with `$logical_value - 1` for + `function_body` and `x` for `destination`.""" + + function_body = Optional(Expression) + destination = Optional(FieldReference) + + +@message +class WriteMethod(Message): + """IR which defines the method used for writing to a virtual field.""" + + # A physical Field can be written directly. + physical = Optional(bool, "method") + + # A read_only Field cannot be written. + read_only = Optional(bool, "method") + + # An alias is a direct, untransformed forward of another field; it can be + # implemented by directly returning a reference to the aliased field. + # + # Aliases are the only kind of virtual field that may have an opaque type. + alias = Optional(FieldReference, "method") + + # A transform is a way of turning a logical value into a value which should + # be written to another field: A virtual field like `let y = x + 1` would + # have a transform WriteMethod to subtract 1 from the new `y` value, and + # write that to `x`. + transform = Optional(WriteTransform, "method") + + +@message +class FieldLocation(Message): + """IR for a field location.""" + start = Optional(Expression) + size = Optional(Expression) + source_location = Optional(Location) + + +@message +class Field(Message): + """IR for a field in a struct definition. + + There are two kinds of Field: physical fields have location and (physical) + type; virtual fields have read_transform. Although there are differences, + in many situations physical and virtual fields are treated the same way, + and they can be freely intermingled in the source file.""" + location = Optional(FieldLocation) # The physical location of the field. + type = Optional(Type) # The physical type of the field. + + read_transform = Optional(Expression) # The value of a virtual field. + + # How this virtual field should be written. + write_method = Optional(WriteMethod) + + name = Optional(NameDefinition) # The name of the field. + abbreviation = Optional(Word) # An optional short name for the field, only + # visible inside the enclosing bits/struct. + attribute = Repeated(Attribute) # Field-specific attributes. + documentation = Repeated(Documentation) # Field-specific documentation. + + # The field only exists when existence_condition evaluates to true. For + # example: + # + # struct Message: + # 0 [+4] UInt length + # 4 [+8] MessageType message_type + # if message_type == MessageType.FOO: + # 8 [+length] Foo foo + # if message_type == MessageType.BAR: + # 8 [+length] Bar bar + # 8+length [+4] UInt crc + # + # For length, message_type, and crc, existence_condition will be + # "boolean_constant { value: true }" + # + # For "foo", existence_condition will be: + # function { function: EQUALITY + # args: [reference to message_type] + # args: { [reference to MessageType.FOO] } } + # + # The "bar" field will have a similar existence_condition to "foo": + # function { function: EQUALITY + # args: [reference to message_type] + # args: { [reference to MessageType.BAR] } } + # + # When message_type is MessageType.BAR, the Message struct does not contain + # field "foo", and vice versa for message_type == MessageType.FOO and field + # "bar": those fields only conditionally exist in the structure. + # + # TODO(bolms): Document conditional fields better, and replace some of this + # explanation with a reference to the documentation. + existence_condition = Optional(Expression) + source_location = Optional(Location) + + +@message +class Structure(Message): + """IR for a bits or struct definition.""" + field = Repeated(Field) + + # The fields in `field` are listed in the order they appear in the original + # .emb. + # + # For text format output, this can lead to poor results. Take the following + # struct: + # + # struct Foo: + # b [+4] UInt a + # 0 [+4] UInt b + # + # Here, the location of `a` depends on the current value of `b`. Because of + # this, if someone calls + # + # emboss::UpdateFromText(foo_view, "{ a: 10, b: 4 }"); + # + # then foo_view will not be updated the way one would expect: if `b`'s value + # was something other than 4 to start with, then `UpdateFromText` will write + # the 10 to some other location, then update `b` to 4. + # + # To avoid surprises, `emboss::DumpAsText` should return `"{ b: 4, a: 10 + # }"`. + # + # The `fields_in_dependency_order` field provides a permutation of `field` + # such that each field appears after all of its dependencies. For example, + # `struct Foo`, above, would have `{ 1, 0 }` in + # `fields_in_dependency_order`. + # + # The exact ordering of `fields_in_dependency_order` is not guaranteed, but + # some effort is made to keep the order close to the order fields are listed + # in the original `.emb` file. In particular, if the ordering 0, 1, 2, 3, + # ... satisfies dependency ordering, then `fields_in_dependency_order` will + # be `{ 0, 1, 2, 3, ... }`. + fields_in_dependency_order = Repeated(int) + + source_location = Optional(Location) + + +@message +class External(Message): + """IR for an external type declaration.""" + # Externals have no values other than name and attribute list, which are + # common to all type definitions. + + source_location = Optional(Location) + + +@message +class EnumValue(Message): + """IR for a single value within an enumerated type.""" + name = Optional(NameDefinition) # The name of the enum value. + value = Optional(Expression) # The value of the enum value. + documentation = Repeated(Documentation) # Value-specific documentation. + + source_location = Optional(Location) + + +@message +class Enum(Message): + """IR for an enumerated type definition.""" + value = Repeated(EnumValue) + source_location = Optional(Location) + + +@message +class Import(Message): + """IR for an import statement in a module.""" + file_name = Optional(String) # The file to import. + local_name = Optional(Word) # The name to use within this module. + source_location = Optional(Location) + + +@message +class RuntimeParameter(Message): + name = Optional(NameDefinition) # The name of the parameter. + type = Optional(ExpressionType) # The type of the parameter. + + # For convenience and readability, physical types may be used in the .emb + # source instead of a full expression type. That way, users can write + # something like: + # + # struct Foo(version :: UInt:8): + # + # instead of: + # + # struct Foo(version :: {$int x |: 0 <= x <= 255}): + # + # In these cases, physical_type_alias holds the user-supplied type, and type + # is filled in after initial parsing is finished. + # + # TODO(bolms): Actually implement the set builder type notation. + physical_type_alias = Optional(Type) + + source_location = Optional(Location) + + +@message +class TypeDefinition(Message): + """Container IR for a type definition (struct, union, etc.)""" + + # The "addressable unit" is the size of the smallest unit that can be read + # from the backing store that this type expects. For `struct`s, this is + # BYTE; for `enum`s and `bits`, this is BIT, and for `external`s it depends + # on the specific type. + + NONE = 0 + BIT = 1 + BYTE = 8 + + external = Optional(External, "type") + enumeration = Optional(Enum, "type") + structure = Optional(Structure, "type") + + name = Optional(NameDefinition) # The name of the type. + attribute = Repeated(Attribute) # All attributes attached to the type. + documentation = Repeated(Documentation) # Docs for the type. + subtype = Repeated(lambda: TypeDefinition) # Subtypes of this type. + addressable_unit = Optional( + int, names=lambda x: getattr(TypeDefinition, x)) + + # If the type requires parameters at runtime, these are its parameters. + # These are currently only allowed on structures, but in the future they + # should be allowed on externals. + runtime_parameter = Repeated(RuntimeParameter) + + source_location = Optional(Location) + + +@message +class Module(Message): + """The IR for an individual Emboss module (file).""" + attribute = Repeated(Attribute) # Module-level attributes. + type = Repeated(TypeDefinition) # Module-level type definitions. + documentation = Repeated(Documentation) # Module-level docs. + foreign_import = Repeated(Import) # Other modules imported. + source_location = Optional(Location) # Source code covered by this IR. + source_file_name = Optional(_text) # Name of the source file. + + +@message +class EmbossIr(Message): + """The top-level IR for an Emboss module and all of its dependencies.""" + # All modules. The first entry will be the main module; back ends should + # generate code corresponding to that module. The second entry will be the + # prelude module. + module = Repeated(Module) + + +_initialize_deferred_specs()