KASSERT  0.0.1
Karlsruhe Assertion Library
expression_decomposition.hpp
Go to the documentation of this file.
1// This file is part of KAssert.
2//
3// Copyright 2021-2022 The KAssert Authors
4
5/// @file
6/// @brief Expression decomposition.
7///
8/// Failed assertions try to expand the expression similar to what Catch2 does. This is achieved by the following
9/// process:
10///
11/// In a call
12/// ```
13/// KASSERT(rhs == lhs)
14/// ```
15/// KASSERT() also prints the values of \c rhs and \c lhs. However, this expression expansion is limited and only works
16/// for expressions that do not contain parentheses, but are implicitly left-associative. This is due to its
17/// implementation:
18/// ```
19/// KASSERT(rhs == lhs)
20/// ```
21/// is replaced by
22/// ```
23/// Decomposer{} <= rhs == lhs
24/// ```
25/// which is interpreted by the compiler as
26/// ```
27/// ((Decomposer{} <= rhs) == lhs)
28/// ```
29/// where the first <= relation is overloaded to return a proxy object which in turn overloads other operators. If the
30/// expression is not implicitly left-associative or contains parentheses, this does not work:
31/// ```
32/// KASSERT(rhs1 == lhs1 && rhs2 == lhs2)
33/// ```
34/// is replaced by (with implicit parentheses)
35/// ```
36/// ((Decomposer{} <= rhs1) == lhs1) && (rhs2 == lhs2))
37/// ```
38/// Thus, the left hand side of \c && can only be expanded to the *result* of `rhs2 == lhs2`.
39/// This limitation only affects the error message, not the interpretation of the expression itself.
40
41#pragma once
42
43#include <type_traits>
44
46
47namespace kassert::internal {
48/// @brief Type trait that is always false, to implement static_asserts that always fail, thus preventing a
49/// template function from being instanciated. Used to forbid calling the overloads of && and ||.
50/// @tparam T Some template parameter of the template that should never be instantiated.
51template <typename T>
52struct AlwaysFalse : public std::false_type {};
53
54/// @brief Interface for decomposed unary and binary expressions.
56public:
57 /// @brief Virtual destructor since we use virtual functions.
58 virtual ~Expression() = default;
59
60 /// @brief Evaluate the assertion wrapped in this Expr.
61 /// @return The boolean value that the assertion evalutes to.
62 [[nodiscard]] virtual bool result() const = 0;
63
64 /// @brief Write this expression with stringified operands to the given assertion logger.
65 /// @param out The assertion logger.
66 virtual void stringify(OStreamLogger& out) const = 0;
67
68 /// @brief Writes an expression with stringified operands to the given assertion logger.
69 /// @param out The assertion logger.
70 /// @param expr The expression to be stringified.
71 /// @return The assertion logger.
72 friend OStreamLogger& operator<<(OStreamLogger& out, Expression const& expr) {
73 expr.stringify(out);
74 return out;
75 }
76};
77
78/// @brief A decomposed binary expression.
79/// @tparam LhsT Decomposed type of the left hand side of the expression.
80/// @tparam RhsT Decomposed type of the right hand side of the expression.
81template <typename LhsT, typename RhsT>
83public:
84 /// @brief Constructs a decomposed binary expression.
85 /// @param result Boolean result of the expression.
86 /// @param lhs Decomposed left hand side of the expression.
87 /// @param op Stringified operator or relation.
88 /// @param rhs Decomposed right hand side of the expression.
89 BinaryExpression(bool const result, LhsT const& lhs, std::string_view const op, RhsT const& rhs)
90 : _result(result),
91 _lhs(lhs),
92 _op(op),
93 _rhs(rhs) {}
94
95 /// @brief The boolean result of the expression. This is used when retrieving the expression result after
96 /// decomposition.
97 /// @return The boolean result of the expression.
98 [[nodiscard]] bool result() const final {
99 return _result;
100 }
101
102 /// @brief Implicitly cast to bool. This is used when encountering && or ||.
103 /// @return The boolean result of the expression.
104 operator bool() {
105 return _result;
106 }
107
108 /// @brief Writes this expression with stringified operands to the given assertion logger.
109 /// @param out The assertion logger.
110 void stringify(OStreamLogger& out) const final {
111 stringify_value(out, _lhs);
112 out << " " << _op << " ";
113 stringify_value(out, _rhs);
114 }
115
116 /// @cond IMPLEMENTATION
117
118 // Overload operators to return a proxy object that decomposes the rhs of the logical operator
119#define KASSERT_ASSERT_OP(op) \
120 template <typename RhsPrimeT> \
121 friend BinaryExpression<BinaryExpression<LhsT, RhsT>, RhsPrimeT> operator op( \
122 BinaryExpression<LhsT, RhsT>&& lhs, \
123 RhsPrimeT const& rhs_prime \
124 ) { \
125 using namespace std::string_view_literals; \
126 return BinaryExpression<BinaryExpression<LhsT, RhsT>, RhsPrimeT>( \
127 lhs.result() op rhs_prime, \
128 lhs, \
129 #op##sv, \
130 rhs_prime \
131 ); \
132 }
133
134 KASSERT_ASSERT_OP(&)
135 KASSERT_ASSERT_OP(|)
136 KASSERT_ASSERT_OP(^)
137 KASSERT_ASSERT_OP(==)
138 KASSERT_ASSERT_OP(!=)
139
140#undef KASSERT_ASSERT_OP
141
142 /// @endcond
143
144private:
145 /// @brief Boolean result of this expression.
146 bool _result;
147 /// @brief Decomposed left hand side of this expression.
148 LhsT const& _lhs;
149 /// @brief Stringified operand or relation symbol.
150 std::string_view _op;
151 /// @brief Right hand side of this expression.
152 RhsT const& _rhs;
153};
154
155/// @brief Decomposed unary expression.
156/// @tparam Lhst Decomposed expression type.
157template <typename LhsT>
159public:
160 /// @brief Constructs this unary expression from an expression.
161 /// @param lhs The expression.
162 explicit UnaryExpression(LhsT const& lhs) : _lhs(lhs) {}
163
164 /// @brief Evaluates this expression.
165 /// @return The boolean result of this expression.
166 [[nodiscard]] bool result() const final {
167 return static_cast<bool>(_lhs);
168 }
169
170 /// @brief Writes this expression with stringified operands to the given assertion logger.
171 /// @param out The assertion logger.
172 void stringify(OStreamLogger& out) const final {
173 stringify_value(out, _lhs);
174 }
175
176private:
177 /// @brief The expression.
178 LhsT const& _lhs;
179};
180
181/// @brief The left hand size of a decomposed expression. This can either be turned into a \c BinaryExpr if an operand
182/// or relation follows, or into a \c UnaryExpr otherwise.
183/// @tparam LhsT The expression type.
184template <typename LhsT>
186public:
187 /// @brief Constructs this left hand size of a decomposed expression.
188 /// @param lhs The wrapped expression.
189 explicit LhsExpression(LhsT const& lhs) : _lhs(lhs) {}
190
191 /// @brief Turns this expression into an \c UnaryExpr. This might only be called if the wrapped expression is
192 /// implicitly convertible to \c bool.
193 /// @return This expression as \c UnaryExpr.
195 static_assert(std::is_convertible_v<LhsT, bool>, "expression must be convertible to bool");
196 return UnaryExpression<LhsT>{_lhs};
197 }
198
199 /// @brief Implicitly cast to bool. This is used when encountering && or ||.
200 /// @return The boolean result of the expression.
201 operator bool() {
202 return _lhs;
203 }
204
205 /// @cond IMPLEMENTATION
206
207 // Overload binary operators to return a proxy object that decomposes the rhs of the operator.
208#define KASSERT_ASSERT_OP(op) \
209 template <typename RhsT> \
210 friend BinaryExpression<LhsT, RhsT> operator op(LhsExpression&& lhs, RhsT const& rhs) { \
211 using namespace std::string_view_literals; \
212 return {lhs._lhs op rhs, lhs._lhs, #op##sv, rhs}; \
213 }
214
215 KASSERT_ASSERT_OP(==)
216 KASSERT_ASSERT_OP(!=)
217 KASSERT_ASSERT_OP(<)
218 KASSERT_ASSERT_OP(<=)
219 KASSERT_ASSERT_OP(>)
220 KASSERT_ASSERT_OP(>=)
221 KASSERT_ASSERT_OP(&)
222 KASSERT_ASSERT_OP(|)
223 KASSERT_ASSERT_OP(^)
224
225#undef KASSERT_ASSERT_OP
226
227 /// @endcond
228
229private:
230 /// @brief The wrapped expression.
231 LhsT const& _lhs;
232};
233
234/// @brief Decomposes an expression (see group description).
236 /// @brief Decomposes an expression (see group description).
237 /// @tparam LhsT The type of the expression.
238 /// @param lhs The left hand side of the expression.
239 /// @return \c lhs wrapped in a \c LhsExpr.
240 template <typename LhsT>
241 friend LhsExpression<LhsT> operator<=(Decomposer&&, LhsT const& lhs) {
242 return LhsExpression<LhsT>(lhs);
243 }
244};
245
246/// @brief If an expression cannot be decomposed (due to && or ||, to preserve short-circuit evaluation), simply return
247/// the result of the assertion.
248/// @param result Result of the assertion.
249/// @return Result of the assertion.
250inline bool finalize_expr(bool const result) {
251 return result;
252}
253
254/// @brief Transforms \c LhsExpression into \c UnaryExpression, does nothing to a \c Expression (see group description).
255/// @tparam ExprT Type of the expression, either \c LhsExpression or a \c BinaryExpression.
256/// @param expr The expression.
257/// @return The expression as some subclass of \c Expression.
258template <typename ExprT>
259decltype(auto) finalize_expr(ExprT&& expr) {
260 if constexpr (std::is_base_of_v<Expression, std::remove_reference_t<std::remove_const_t<ExprT>>>) {
261 return std::forward<ExprT>(expr);
262 } else {
263 return expr.make_unary();
264 }
265}
266} // namespace kassert::internal
Simple wrapper for output streams that is used to stringify values in assertions and exceptions.
Definition: logger.hpp:47
A decomposed binary expression.
Definition: expression_decomposition.hpp:82
void stringify(OStreamLogger &out) const final
Writes this expression with stringified operands to the given assertion logger.
Definition: expression_decomposition.hpp:110
BinaryExpression(bool const result, LhsT const &lhs, std::string_view const op, RhsT const &rhs)
Constructs a decomposed binary expression.
Definition: expression_decomposition.hpp:89
bool result() const final
The boolean result of the expression. This is used when retrieving the expression result after decomp...
Definition: expression_decomposition.hpp:98
Interface for decomposed unary and binary expressions.
Definition: expression_decomposition.hpp:55
virtual void stringify(OStreamLogger &out) const =0
Write this expression with stringified operands to the given assertion logger.
friend OStreamLogger & operator<<(OStreamLogger &out, Expression const &expr)
Writes an expression with stringified operands to the given assertion logger.
Definition: expression_decomposition.hpp:72
virtual ~Expression()=default
Virtual destructor since we use virtual functions.
virtual bool result() const =0
Evaluate the assertion wrapped in this Expr.
The left hand size of a decomposed expression. This can either be turned into a BinaryExpr if an oper...
Definition: expression_decomposition.hpp:185
LhsExpression(LhsT const &lhs)
Constructs this left hand size of a decomposed expression.
Definition: expression_decomposition.hpp:189
UnaryExpression< LhsT > make_unary()
Turns this expression into an UnaryExpr. This might only be called if the wrapped expression is impli...
Definition: expression_decomposition.hpp:194
Decomposed unary expression.
Definition: expression_decomposition.hpp:158
UnaryExpression(LhsT const &lhs)
Constructs this unary expression from an expression.
Definition: expression_decomposition.hpp:162
bool result() const final
Evaluates this expression.
Definition: expression_decomposition.hpp:166
void stringify(OStreamLogger &out) const final
Writes this expression with stringified operands to the given assertion logger.
Definition: expression_decomposition.hpp:172
bool finalize_expr(bool const result)
If an expression cannot be decomposed (due to && or ||, to preserve short-circuit evaluation),...
Definition: expression_decomposition.hpp:250
void stringify_value(Logger< StreamT > &out, ValueT const &value)
Stringify a value using the given assertion logger. If the value cannot be streamed into the logger,...
Definition: logger.hpp:103
Logger utility class to build error messages for failed assertins.
Type trait that is always false, to implement static_asserts that always fail, thus preventing a temp...
Definition: expression_decomposition.hpp:52
Decomposes an expression (see group description).
Definition: expression_decomposition.hpp:235
friend LhsExpression< LhsT > operator<=(Decomposer &&, LhsT const &lhs)
Decomposes an expression (see group description).
Definition: expression_decomposition.hpp:241