add Chapter18
This commit is contained in:
179
Chapter18/cib/libs/sc/README.md
Normal file
179
Chapter18/cib/libs/sc/README.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# string_constant
|
||||
|
||||
An `sc::string_constant` is a compile-time string in the same way
|
||||
that `std::integral_constant` is a compile-time integral value. When firmware
|
||||
needs to emit a log message, an integral ID is emitted instead of the string
|
||||
data. This conserves both runtime and storage resources. When decoding the log
|
||||
messages, a catalog file is needed to map the IDs back to their string values.
|
||||
|
||||
There are two challenges with such a system. The first is assigning a unique ID
|
||||
to each string value and the second is how to support rich string operations
|
||||
like format and concatenation.
|
||||
|
||||
The `sc::string_constant` library solves both of these challenges by
|
||||
representing the value of the string in its type. The `sc::string_constant`'s
|
||||
string template struct is parameterized by a list of chars representing the
|
||||
string value as well as a tuple of zero or more dynamic arguments to be
|
||||
formatted by a log decoder.
|
||||
|
||||
The details of this implementation are provided in the Theory of Operation
|
||||
section.
|
||||
|
||||
[](https://www.youtube.com/watch?v=Dt0vx-7e_B0)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### `_sc` user defined literal
|
||||
|
||||
`sc::string_constant` uses a user-defined literal to make it easy to define a
|
||||
string:
|
||||
|
||||
```c++
|
||||
constexpr auto hello_world = "Hello, World!"_sc;
|
||||
```
|
||||
|
||||
Note how the auto keyword is used for the string type. This is necessary because
|
||||
the string's type changes depending on its value. It would be redundant and
|
||||
burdensome to specify the exact type.
|
||||
|
||||
Also note the `constexpr` keyword is used. `sc::string_constant`s are fully
|
||||
`constexpr` and nearly all operations are performed at compile-time even without
|
||||
the `constexpr` keyword.
|
||||
|
||||
Because the string value is represented by its type, strings can be assigned as
|
||||
type aliases or passed in as template parameters.
|
||||
|
||||
```c++
|
||||
using HelloWorldType = decltype("Hello, World!"_sc);
|
||||
|
||||
constexpr HelloWorldType helloWorld{};
|
||||
|
||||
static_assert(helloWorld == "Hello, World!"_sc);
|
||||
```
|
||||
|
||||
### Concatenation
|
||||
|
||||
Two strings can be joined together using the `+` operator:
|
||||
|
||||
```c++
|
||||
static_assert("Hi "_sc + "Emily!"_sc == "Hi Emily!"_sc);
|
||||
```
|
||||
|
||||
### Format
|
||||
|
||||
`sc::string_constant`s can be formatted using a subset of python
|
||||
or `fmt::format`
|
||||
format specifiers. Formatting behavior is different depending on whether the
|
||||
arguments are compile-time values or dynamic values. Compile-time values will be
|
||||
formatted at compile time while dynamic values will only be formatted by tools
|
||||
outside of firmware like a log decoder.
|
||||
|
||||
```c++
|
||||
constexpr auto my_age = 6;
|
||||
constexpr auto day_to_go = day_of_week::SATURDAY;
|
||||
auto my_name = "Olivia"_sc;
|
||||
using BirthdayParty = party<party_type::Birthday, day_of_week::TUESDAY>;
|
||||
|
||||
// use int_<value> to format an integer known at compile time
|
||||
static_assert(
|
||||
format("I am {} years old."_sc, sc::int_<my_age>) ==
|
||||
"I am 6 years old."_sc);
|
||||
|
||||
// use enum_<value> to format an enum value known at compile time
|
||||
static_assert(
|
||||
format("Let's go on {}."_sc, sc::enum_<day_to_go>) ==
|
||||
"Let's go on SATURDAY."_sc);
|
||||
|
||||
// strings can be used as format arguments as well
|
||||
static_assert(
|
||||
format("My name is {}."_sc, my_name) ==
|
||||
"My name is Olivia."_sc);
|
||||
|
||||
// use type_<type> to get the string of a type which can then be used as a format argument
|
||||
static_assert(
|
||||
type_<BirthdayParty> ==
|
||||
"party<party_type::Birthday, day_of_week::TUESDAY>"_sc);
|
||||
|
||||
// multiple arguments can be formatted at once
|
||||
static_assert(
|
||||
format("My name is {} and I am {} years old."_sc, my_name, my_age) ==
|
||||
"My name is Olivia and I am 6 years old."_sc);
|
||||
|
||||
// runtime arguments not known at compile-time can also be formatted. the dynamic values can be
|
||||
// accessed at runtime and emitted along with the string_constant id.
|
||||
void read_memory(std::uint32_t addr) {
|
||||
auto const my_message = format("Reading memory at {:08x}"_sc, addr);
|
||||
}
|
||||
|
||||
// both runtime and compile time arguments can be used in a single format operation
|
||||
template<typename RegType>
|
||||
void readReg() {
|
||||
RegType reg{};
|
||||
auto const reg_value = apply(read(reg.raw()));
|
||||
auto const my_message = format("Register {} = {}"_sc, type_<RegType>, reg_value);
|
||||
}
|
||||
```
|
||||
|
||||
Note that values known at compile time are passed into template variables as
|
||||
template parameters. This allows the string.format method to access constexpr
|
||||
versions of the values and format them into the string.
|
||||
|
||||
If you format integers or enums without using the template variables, then the
|
||||
formatting happens outside of firmware by the log decoder collateral.
|
||||
|
||||
## Theory of Operation
|
||||
|
||||
Compile-time string literals are created using user-defined string template
|
||||
literals. These are not yet part of a C++ standard but are supported by GCC,
|
||||
Clang, and MSVC. They allow for easy conversion of a string literal to template
|
||||
parameters.
|
||||
|
||||
```c++
|
||||
template<class T, T... chars>
|
||||
constexpr auto operator""_sc() {
|
||||
return sc::string_constant<T, chars...>{};
|
||||
}
|
||||
```
|
||||
|
||||
We can see clearly that the string value is encoded in the type.
|
||||
|
||||
```c++
|
||||
// create a compile-time string. note that it does not need to be constexpr or even const because the value is encoded in the type.
|
||||
auto hello_value = "Hello!"_sc;
|
||||
using HelloType = decltype(hello_value);
|
||||
|
||||
// the string's value is represented by its type
|
||||
using ExpectedType = sc::string_constant<char, 'H', 'e', 'l', 'l', 'o', '!'>;
|
||||
|
||||
ExpectedType expected_value{};
|
||||
|
||||
// same type
|
||||
static_assert(std::is_same_v<HelloType, ExpectedType>);
|
||||
|
||||
// same value
|
||||
static_assert(hello_value == expected_value);
|
||||
```
|
||||
|
||||
Encoding the string value in the type is necessary for string_constant support.
|
||||
Type information is used when linking object files together. This means the
|
||||
string values can be present in object files to be extracted during the build
|
||||
process. The other part is assigning a unique ID to each string. This can be
|
||||
done with the declaration of a template function with external linkage like
|
||||
this:
|
||||
|
||||
```c++
|
||||
template<typename StringType>
|
||||
extern std::uint32_t get_string_id(StringType);
|
||||
```
|
||||
|
||||
When a string needs to be emitted as part of a log message, the `get_string_id()`
|
||||
function is used to translate the string type to its ID. During compilation, an
|
||||
intermediate object file is generated missing a definition for each
|
||||
instantiation of the `get_string_id()` function. This information can be extracted
|
||||
using the 'nm' tool and the original string values recovered. A
|
||||
new `strings.cpp` file can be generated that implements all the template
|
||||
instantiations returning a unique ID for each string. When the original object
|
||||
binary is linked to `strings.o`, the calls to `get_string_id()` can be replaced
|
||||
with the actual int value.
|
||||
|
||||
103
Chapter18/cib/libs/sc/format.hpp
Normal file
103
Chapter18/cib/libs/sc/format.hpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <sc/fwd.hpp>
|
||||
#include <sc/lazy_string_format.hpp>
|
||||
#include <sc/string_constant.hpp>
|
||||
|
||||
#include <stdx/compiler.hpp>
|
||||
#include <stdx/ct_conversions.hpp>
|
||||
#include <stdx/tuple.hpp>
|
||||
#include <stdx/tuple_algorithms.hpp>
|
||||
|
||||
#include <fmt/compile.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace sc {
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
concept compile_time_field =
|
||||
std::same_as<typename T::value_type,
|
||||
std::remove_cvref_t<decltype(T::value)>>;
|
||||
|
||||
template <compile_time_field T> [[nodiscard]] CONSTEVAL auto field_value(T) {
|
||||
if constexpr (std::is_enum_v<typename T::value_type>) {
|
||||
return stdx::enum_as_string<T::value>();
|
||||
} else {
|
||||
return T::value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] CONSTEVAL auto field_value(sc::type_name<T>) {
|
||||
return stdx::type_as_string<T>();
|
||||
}
|
||||
|
||||
template <typename Fmt, typename Arg> constexpr auto format1(Fmt, Arg arg) {
|
||||
constexpr auto str = [&] {
|
||||
constexpr auto fmtstr = FMT_COMPILE(Fmt::value);
|
||||
constexpr auto sz = fmt::formatted_size(fmtstr, field_value(arg));
|
||||
std::array<char, sz> buf{};
|
||||
fmt::format_to(std::begin(buf), fmtstr, field_value(arg));
|
||||
return buf;
|
||||
}();
|
||||
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
|
||||
return string_constant<char, str[Is]...>{};
|
||||
}(std::make_index_sequence<std::size(str)>{});
|
||||
}
|
||||
|
||||
template <typename Fmt> constexpr auto split_format_spec() {
|
||||
constexpr Fmt fmt{};
|
||||
constexpr auto spec_start = std::adjacent_find(
|
||||
std::begin(fmt), std::end(fmt),
|
||||
[](auto c1, auto c2) { return c1 == '{' and c2 != '{'; });
|
||||
if constexpr (spec_start == std::end(fmt)) {
|
||||
return std::pair{fmt, ""_sc};
|
||||
} else {
|
||||
constexpr auto spec_end = std::find_if(spec_start, std::end(fmt),
|
||||
[](auto c) { return c == '}'; });
|
||||
constexpr auto len =
|
||||
static_cast<int>(std::distance(std::begin(fmt), spec_end) + 1);
|
||||
return std::pair{fmt.substr(int_<0>, int_<len>), fmt.substr(int_<len>)};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Str, typename Fmt, typename RuntimeTuple, typename Arg>
|
||||
constexpr auto process_arg(stdx::tuple<Str, Fmt, RuntimeTuple> t, Arg arg) {
|
||||
using namespace stdx::literals;
|
||||
constexpr auto p = split_format_spec<Fmt>();
|
||||
if constexpr (requires { field_value(arg); }) {
|
||||
return stdx::make_tuple(t[0_idx] + format1(p.first, arg), p.second,
|
||||
t[2_idx]);
|
||||
} else if constexpr (requires { arg.args; }) {
|
||||
return stdx::make_tuple(t[0_idx] + format1(p.first, arg.str), p.second,
|
||||
stdx::tuple_cat(t[2_idx], arg.args));
|
||||
} else {
|
||||
return stdx::make_tuple(
|
||||
t[0_idx] + p.first, p.second,
|
||||
stdx::tuple_cat(t[2_idx], stdx::make_tuple(arg)));
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename Fmt, typename... Args>
|
||||
constexpr auto format(Fmt, Args... args) {
|
||||
using namespace stdx::literals;
|
||||
auto t = stdx::make_tuple(args...);
|
||||
auto r =
|
||||
t.fold_left(stdx::make_tuple(""_sc, Fmt{}, stdx::tuple{}),
|
||||
[](auto x, auto y) { return detail::process_arg(x, y); });
|
||||
if constexpr (r[2_idx].size() == 0) {
|
||||
return r[0_idx] + r[1_idx];
|
||||
} else {
|
||||
return lazy_string_format{r[0_idx] + r[1_idx], r[2_idx]};
|
||||
}
|
||||
}
|
||||
} // namespace sc
|
||||
46
Chapter18/cib/libs/sc/fwd.hpp
Normal file
46
Chapter18/cib/libs/sc/fwd.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdx/compiler.hpp>
|
||||
#include <stdx/ct_string.hpp>
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
namespace sc {
|
||||
template <std::signed_integral auto value>
|
||||
constexpr static std::integral_constant<decltype(value), value> int_{};
|
||||
|
||||
template <std::unsigned_integral auto value>
|
||||
constexpr static std::integral_constant<decltype(value), value> uint_{};
|
||||
|
||||
template <bool value>
|
||||
constexpr static std::integral_constant<bool, value> bool_{};
|
||||
|
||||
template <char value>
|
||||
constexpr static std::integral_constant<char, value> char_{};
|
||||
|
||||
template <auto enumValue>
|
||||
requires std::is_enum_v<decltype(enumValue)>
|
||||
constexpr static std::integral_constant<decltype(enumValue), enumValue> enum_{};
|
||||
|
||||
template <typename T> struct type_name {
|
||||
constexpr explicit type_name(T) {}
|
||||
constexpr type_name() = default;
|
||||
};
|
||||
|
||||
template <typename T> constexpr static type_name<T> type_{};
|
||||
|
||||
template <typename CharT, CharT... chars> struct string_constant;
|
||||
|
||||
inline namespace literals {
|
||||
template <stdx::ct_string S> CONSTEVAL auto operator""_sc() {
|
||||
return stdx::ct_string_to_type<S, string_constant>();
|
||||
}
|
||||
} // namespace literals
|
||||
|
||||
template <typename T>
|
||||
concept sc_like = requires(T const &t) {
|
||||
t.apply([]<typename StringType>(StringType, auto const &...) {});
|
||||
};
|
||||
} // namespace sc
|
||||
using sc::literals::operator""_sc;
|
||||
56
Chapter18/cib/libs/sc/lazy_string_format.hpp
Normal file
56
Chapter18/cib/libs/sc/lazy_string_format.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <sc/fwd.hpp>
|
||||
|
||||
#include <stdx/tuple_algorithms.hpp>
|
||||
|
||||
namespace sc {
|
||||
template <typename StringConstant, typename ArgTuple>
|
||||
struct lazy_string_format {
|
||||
constexpr static StringConstant str{};
|
||||
ArgTuple args{};
|
||||
|
||||
constexpr lazy_string_format() = default;
|
||||
constexpr lazy_string_format(StringConstant, ArgTuple newArgs)
|
||||
: args{newArgs} {}
|
||||
|
||||
template <typename F> constexpr auto apply(F &&f) const {
|
||||
return args.apply(
|
||||
[&](auto const &...as) { return std::forward<F>(f)(str, as...); });
|
||||
}
|
||||
};
|
||||
|
||||
template <class CharT, CharT... charsLhs, typename ArgsTupleLhs,
|
||||
CharT... charsRhs, typename ArgsTupleRhs>
|
||||
[[nodiscard]] constexpr auto operator==(
|
||||
lazy_string_format<string_constant<CharT, charsLhs...>, ArgsTupleLhs> lhs,
|
||||
lazy_string_format<string_constant<CharT, charsRhs...>, ArgsTupleRhs>
|
||||
rhs) noexcept -> bool {
|
||||
return (lhs.str == rhs.str) && (lhs.args == rhs.args);
|
||||
}
|
||||
|
||||
template <typename StringConstantLhs, typename TupleArgsLhs,
|
||||
typename StringConstantRhs, typename TupleArgsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator+(lazy_string_format<StringConstantLhs, TupleArgsLhs> lhs,
|
||||
lazy_string_format<StringConstantRhs, TupleArgsRhs> rhs) noexcept {
|
||||
return lazy_string_format{lhs.str + rhs.str,
|
||||
stdx::tuple_cat(lhs.args, rhs.args)};
|
||||
}
|
||||
|
||||
template <typename StringConstantLhs, typename TupleArgsLhs, typename CharT,
|
||||
CharT... chars>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator+(lazy_string_format<StringConstantLhs, TupleArgsLhs> lhs,
|
||||
string_constant<CharT, chars...> rhs) noexcept {
|
||||
return lazy_string_format{lhs.str + rhs, lhs.args};
|
||||
}
|
||||
|
||||
template <typename CharT, CharT... chars, typename StringConstantRhs,
|
||||
typename TupleArgsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator+(string_constant<CharT, chars...> lhs,
|
||||
lazy_string_format<StringConstantRhs, TupleArgsRhs> rhs) noexcept {
|
||||
return lazy_string_format{lhs + rhs.str, rhs.args};
|
||||
}
|
||||
} // namespace sc
|
||||
108
Chapter18/cib/libs/sc/string_constant.hpp
Normal file
108
Chapter18/cib/libs/sc/string_constant.hpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <sc/fwd.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace sc {
|
||||
template <typename CharT, CharT... chars> struct string_constant {
|
||||
using value_type = std::basic_string_view<CharT>;
|
||||
|
||||
private:
|
||||
constexpr static std::array<CharT, sizeof...(chars)> storage{chars...};
|
||||
|
||||
using size_type = int;
|
||||
using const_iterator = typename value_type::const_iterator;
|
||||
constexpr static size_type npos = std::numeric_limits<size_type>::max();
|
||||
|
||||
public:
|
||||
constexpr static value_type value{storage.data(), sizeof...(chars)};
|
||||
|
||||
constexpr static auto begin() noexcept { return std::cbegin(storage); }
|
||||
constexpr static auto end() noexcept { return std::cend(storage); }
|
||||
|
||||
[[nodiscard]] constexpr static auto size() noexcept {
|
||||
return std::size(storage);
|
||||
}
|
||||
|
||||
template <size_type pos = 0, size_type count = npos>
|
||||
[[nodiscard]] constexpr static auto
|
||||
substr(std::integral_constant<size_type, pos>,
|
||||
std::integral_constant<size_type, count> = {}) {
|
||||
constexpr size_type sz = count == npos ? size() - pos : count;
|
||||
return [&]<size_type... Is>(std::integer_sequence<size_type, Is...>) {
|
||||
return string_constant<CharT, storage[pos + Is]...>{};
|
||||
}(std::make_integer_sequence<size_type, sz>{});
|
||||
}
|
||||
|
||||
template <typename F> constexpr auto apply(F &&f) const {
|
||||
return std::forward<F>(f)(*this);
|
||||
}
|
||||
};
|
||||
|
||||
template <class CharT, CharT... charsLhs, CharT... charsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator==(string_constant<CharT, charsLhs...>,
|
||||
string_constant<CharT, charsRhs...>) noexcept -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class CharT, CharT... chars>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator==(string_constant<CharT, chars...>,
|
||||
string_constant<CharT, chars...>) noexcept -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if __cpp_lib_three_way_comparison < 201907L
|
||||
template <class CharT, CharT... charsLhs, CharT... charsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator!=(string_constant<CharT, charsLhs...> lhs,
|
||||
string_constant<CharT, charsRhs...> rhs) noexcept -> bool {
|
||||
return not(lhs == rhs);
|
||||
}
|
||||
template <class CharT, CharT... charsLhs, CharT... charsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator<(string_constant<CharT, charsLhs...> lhs,
|
||||
string_constant<CharT, charsRhs...> rhs) noexcept -> bool {
|
||||
return lhs.value < rhs.value;
|
||||
}
|
||||
template <class CharT, CharT... charsLhs, CharT... charsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator>(string_constant<CharT, charsLhs...> lhs,
|
||||
string_constant<CharT, charsRhs...> rhs) noexcept -> bool {
|
||||
return lhs.value > rhs.value;
|
||||
}
|
||||
template <class CharT, CharT... charsLhs, CharT... charsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator<=(string_constant<CharT, charsLhs...> lhs,
|
||||
string_constant<CharT, charsRhs...> rhs) noexcept -> bool {
|
||||
return lhs.value <= rhs.value;
|
||||
}
|
||||
template <class CharT, CharT... charsLhs, CharT... charsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator>=(string_constant<CharT, charsLhs...> lhs,
|
||||
string_constant<CharT, charsRhs...> rhs) noexcept -> bool {
|
||||
return lhs.value >= rhs.value;
|
||||
}
|
||||
#else
|
||||
template <class CharT, CharT... charsLhs, CharT... charsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator<=>(string_constant<CharT, charsLhs...> lhs,
|
||||
string_constant<CharT, charsRhs...> rhs) noexcept {
|
||||
return lhs.value <=> rhs.value;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class CharT, CharT... charsLhs, CharT... charsRhs>
|
||||
[[nodiscard]] constexpr auto
|
||||
operator+(string_constant<CharT, charsLhs...>,
|
||||
string_constant<CharT, charsRhs...>) noexcept
|
||||
-> string_constant<CharT, charsLhs..., charsRhs...> {
|
||||
return {};
|
||||
}
|
||||
} // namespace sc
|
||||
Reference in New Issue
Block a user