AsmGrader 0.0.0
Loading...
Searching...
No Matches
requirement.hpp
Go to the documentation of this file.
1#pragma once
2
12#include <asmgrader/logging.hpp>
14
15#include <boost/mp11/detail/mp_list.hpp>
16#include <boost/mp11/detail/mp_map_find.hpp>
17#include <boost/mp11/list.hpp>
18#include <boost/mp11/map.hpp>
19#include <boost/type_index.hpp>
20#include <fmt/format.h>
21#include <fmt/ranges.h>
22#include <gsl/util>
23#include <libassert/assert.hpp>
24#include <range/v3/action/take_while.hpp>
25#include <range/v3/algorithm/any_of.hpp>
26#include <range/v3/algorithm/contains.hpp>
27#include <range/v3/algorithm/find.hpp>
28#include <range/v3/algorithm/find_if.hpp>
29#include <range/v3/algorithm/find_if_not.hpp>
30#include <range/v3/range/access.hpp>
31#include <range/v3/range/concepts.hpp>
32#include <range/v3/range/conversion.hpp>
33#include <range/v3/view/drop_while.hpp>
34#include <range/v3/view/split.hpp>
35#include <range/v3/view/split_when.hpp>
36#include <range/v3/view/take_while.hpp>
37
38#include <array>
39#include <concepts>
40#include <cstddef>
41#include <functional>
42#include <string>
43#include <string_view>
44#include <tuple>
45#include <type_traits>
46#include <utility>
47#include <variant>
48#include <vector>
49
50#include <meta/meta.hpp>
51
52namespace asmgrader {
53
55namespace exprs {
56
57enum class ArityKind { Nullary = 0, Unary, Binary, Ternary };
58
59template <typename OpFn, StaticString Rep, typename... Args>
60struct NAryOp
61{
62 // NOLINTNEXTLINE(readability-identifier-naming)
63 constexpr NAryOp(std::tuple<Args...> args_, std::array<inspection::Tokenizer<>, sizeof...(Args)> arg_tokens_)
64 : args{std::move(args_)}
65 , arg_tokens{std::move(arg_tokens_)} {}
66
67 // FIXME: Making a copy of everything
68 std::tuple<Args...> args;
69 std::array<inspection::Tokenizer<>, sizeof...(Args)> arg_tokens;
70
71 using EvalResT = std::decay_t<std::invoke_result_t<OpFn, Args...>>;
72
73 // FIXME: may be evaluated multiple times
74 EvalResT eval() const { return std::apply(OpFn{}, args); }
75
76 static constexpr std::string_view raw_str = Rep;
77
78 static constexpr ArityKind arity{sizeof...(Args)};
79};
80
82template <template <typename...> typename Op, typename... Args>
83constexpr auto make(std::array<inspection::Tokenizer<>, sizeof...(Args)> arg_tokens, Args&&... args) {
84 return Op<Args...>{{std::forward<Args>(args)...}, arg_tokens};
85}
86
87template <typename Args>
88using Noop = NAryOp<std::identity, "", Args>;
89
90template <typename Arg>
92
93template <typename Lhs, typename Rhs>
94using Equal = NAryOp<std::equal_to<>, "==", Lhs, Rhs>;
95
96template <typename Lhs, typename Rhs>
97using NotEqual = NAryOp<std::not_equal_to<>, "!=", Lhs, Rhs>;
98
99template <typename Lhs, typename Rhs>
100using Less = NAryOp<std::less<>, "<", Lhs, Rhs>;
101
102template <typename Lhs, typename Rhs>
103using LessEqual = NAryOp<std::less_equal<>, "<=", Lhs, Rhs>;
104
105template <typename Lhs, typename Rhs>
106using Greater = NAryOp<std::greater<>, ">", Lhs, Rhs>;
107
108template <typename Lhs, typename Rhs>
110
111template <typename T>
112concept Operator = requires(T op) {
113 { T::raw_str } -> std::convertible_to<std::string_view>;
114 { T::arity } -> std::convertible_to<ArityKind>;
115 { op.args } -> IsTemplate<std::tuple>;
116 { op.arg_tokens } -> std::convertible_to<std::array<inspection::Tokenizer<>, static_cast<std::size_t>(T::arity)>>;
117 { op.eval() };
118} && std::tuple_size_v<decltype(T::args)> == static_cast<std::size_t>(T::arity);
119
120// sanity checks
121static_assert(Operator<LogicalNot<int>>);
122static_assert(Operator<Equal<int, int>>);
123
124namespace detail {
125
126template <StaticString Str>
128{
129};
130
131template <typename T, typename U>
132using StrToOpTMap = boost::mp11::mp_list< //
133 std::pair<MapKeyT<"==">, Equal<T, U>>, //
134 std::pair<MapKeyT<"!=">, NotEqual<T, U>>, //
135 std::pair<MapKeyT<"<">, Less<T, U>>, //
136 std::pair<MapKeyT<"<=">, LessEqual<T, U>>, //
137 std::pair<MapKeyT<">">, Greater<T, U>>, //
138 std::pair<MapKeyT<">=">, GreaterEqual<T, U>> //
139 >;
140
141static_assert(boost::mp11::mp_is_map<StrToOpTMap<int, int>>::value);
142
143} // namespace detail
144
145template <StaticString OpStr, typename T, typename U>
146using OpStrToType = boost::mp11::mp_second<boost::mp11::mp_map_find<detail::StrToOpTMap<T, U>, detail::MapKeyT<OpStr>>>;
147
148static_assert(std::same_as<OpStrToType<"!=", int, int>, NotEqual<int, int>>);
149
152{
153 // FIXME: compiler no like [deletes default ctor of variant]
154 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
155 struct Repr
156 {
159
162
164 std::string_view raw_str;
165
166 using TokenStream = std::vector<asmgrader::inspection::Token>;
169
171 boost::typeindex::type_index type_index;
172
174 enum class Type { String, Integer, Floating, Other };
176
181 // TODO: remove this?
183 };
184
185 struct Value
186 {
188 };
189
190 // forward decl for use in Expression
191 struct Operator;
192
193 using Expression = std::variant<Value, Operator>;
194
196 struct Operator
197 {
199
202 std::vector<Expression> operands;
203 };
204
206};
207
208} // namespace exprs
209
210// TODO: Might want to optimize by making more use of views, as the primary use case will be in REQUIRE*
211
212template <exprs::Operator Op>
214{
215public:
216 static constexpr auto default_description = "<no description provided>";
217
218 [[deprecated("====================================================================================================="
219 "=========================================================== !!!!!!!!!!!!!!!!! "
220 "Please consider providing a requirement description by using REQUIRE(..., \"<description here>\")"
221 " !!!!!!!!!!!!!!!!! "
222 "====================================================================================================="
223 "=========================================================== ")]]
224 explicit Requirement(Op op)
226
227 template <StaticString OpStr, typename... Ts>
228 [[deprecated("====================================================================================================="
229 "=========================================================== !!!!!!!!!!!!!!!!! "
230 "Please consider providing a requirement description by using REQUIRE(..., \"<description here>\")"
231 " !!!!!!!!!!!!!!!!! "
232 "====================================================================================================="
233 "=========================================================== ")]]
234 explicit Requirement(const DecomposedExpr<OpStr, Ts...>& decomposed_expr, const inspection::Tokenizer<>& tokens)
235 : Requirement(decomposed_expr, tokens, default_description) {}
236
237 template <StaticString OpStr, typename... Ts>
238 explicit Requirement(const DecomposedExpr<OpStr, Ts...>& decomposed_expr, const inspection::Tokenizer<>& tokens,
239 std::string description)
240 : Requirement(decomposed_to_op(decomposed_expr, tokens), std::move(description)) {}
241
242 explicit Requirement(Op op, std::string description)
243 : op_{op}
244 , description_{std::move(description)}
245 , res_{op.eval()} {}
246
247 std::string get_description() const { return description_; }
248
249 bool get_res() const { return static_cast<bool>(res_); }
250
251 // TODO: Special case for enum / enum class enumerators. Count as literal heuristic: no operators other than `::`?
253 // Only supporting a single unary or binary op for now, so this is pretty simple
254
255 using enum exprs::ArityKind;
256
257 if constexpr (Op::arity != Unary && Op::arity != Binary) {
258 UNIMPLEMENTED("Operators that are not unary or binary are not supported");
259 }
260
261 // Special case for noop to just get the value
262 if constexpr (IsTemplate<Op, exprs::Noop>) {
263 using Arg0T = std::tuple_element<0, decltype(op_.args)>;
264 std::string_view arg0_str = op_.arg_tokens.at(0).get_original();
265
266 return exprs::ExpressionRepr{.expression = make_expr_value<Arg0T>(std::get<0>(op_.args), arg0_str)};
267 } else {
268 using ResultT = decltype(res_);
269 exprs::ExpressionRepr::Repr repr{.repr = "==", //
270 .str = stringize::str(res_),
271 .raw_str = Op::raw_str,
272 .raw_str_tokens =
273 inspection::Tokenizer<>{Op::raw_str} | ranges::to<std::vector>(),
274 .type_index = boost::typeindex::type_id_with_cvr<ResultT>(),
275 .kind = deduce_kind<ResultT>(),
276 .is_literal = false};
277
278 std::vector operands = std::apply(
279 [this]<typename... TupleTypes>(const std::tuple<TupleTypes...>& /*for type deduction*/) {
280 return [this]<typename... Args>(Args&&... args) {
281 std::size_t idx = 0;
282 return std::vector{
283 make_expr_value<TupleTypes>(std::forward<Args>(args), op_.arg_tokens.at(idx++))...};
284 };
285 }(op_.args),
286 op_.args);
287
289 .repr = std::move(repr), .operands = std::move(operands)}};
290 }
291 }
292
293private:
294 template <StaticString OpStr, typename... Ts>
295 static Op decomposed_to_op(const DecomposedExpr<OpStr, Ts...>& decomposed_expr,
296 const inspection::Tokenizer<>& tokens) {
297 std::array<inspection::Tokenizer<>, sizeof...(Ts)> split_tokens_arr{};
298
299 if constexpr (sizeof...(Ts) == 1) {
300 split_tokens_arr.front() = tokens;
301 } else {
302 auto split_pos = gsl::narrow_cast<std::size_t>(
303 ranges::find_if(tokens,
304 [](const inspection::Token& tok) { return tok.str == std::string_view{OpStr}; }) -
305 ranges::begin(tokens));
306 split_tokens_arr.at(0) = tokens.subseq(0, split_pos);
307 split_tokens_arr.at(1) = tokens.subseq(split_pos + 1, tokens.size());
308 }
309
310 return Op(decomposed_expr.operands, split_tokens_arr);
311 }
312
313 template <typename Val>
314 static consteval exprs::ExpressionRepr::Repr::Type deduce_kind() {
315 using Repr = exprs::ExpressionRepr::Repr;
316 using enum Repr::Type;
317
318 using DecayT = std::decay_t<Val>;
319
320 if constexpr (std::convertible_to<DecayT, std::string_view>) {
321 return String;
322 } else if constexpr (std::integral<DecayT>) {
323 return Integer;
324 } else if constexpr (std::floating_point<DecayT>) {
325 return Floating;
326 } else {
327 return Other;
328 }
329 }
330
331 template <typename OrigType, typename Val>
332 exprs::ExpressionRepr::Repr make_repr_value(Val&& value, inspection::Tokenizer<> tokens,
333 [[maybe_unused]] bool is_lvalue) const {
334
335 // TODO: What about suffixes, which are parsed as identifiers
336 // e.g., "Hello!"sv
337 // that probably shouldn't be rendered seperately in output, but that's hard to show in general
338 // maybe don't render it if it's a stdlib defined token (no _)?
339 bool is_literal = !ranges::any_of(
340 tokens, [](const inspection::Token& tok) { return tok.kind == inspection::Token::Kind::Identifier; });
341
342 return {.repr = stringize::repr(tokens, tokens.get_original(), std::forward<Val>(value)),
343 .str = stringize::str(std::forward<Val>(value)),
344 .raw_str = tokens.get_original(),
345 .raw_str_tokens = tokens | ranges::to<std::vector>(),
346 .type_index = boost::typeindex::type_id_with_cvr<OrigType>(),
347 .kind = deduce_kind<OrigType>(),
348 .is_literal = is_literal};
349 }
350
351 // template <typename Val>
352 // exprs::ExpressionRepr::Expression make_expr_value(Val& value, std::string_view raw_str) const {
353 // using Value = exprs::ExpressionRepr::Value;
354 //
355 // return Value{.repr = make_repr_value(value, raw_str, true)};
356 // }
357
358 template <typename OrigType, typename Val>
359 exprs::ExpressionRepr::Expression make_expr_value(Val&& value, inspection::Tokenizer<> tokens) const {
360 using Value = exprs::ExpressionRepr::Value;
361
362 bool is_lvalue{};
363
364 if constexpr (std::is_lvalue_reference_v<OrigType>) {
365 is_lvalue = true;
366 } else {
367 ASSERT(!std::is_reference_v<OrigType>, boost::typeindex::type_id_with_cvr<Val>());
368 is_lvalue = false;
369 }
370
371 return Value{.repr = make_repr_value<OrigType>(std::forward<Val>(value), tokens, is_lvalue)};
372 }
373
374 Op op_;
375 std::invoke_result_t<decltype(&Op::eval), Op> res_;
376 std::string description_;
377
378 static_assert(
379 requires { static_cast<bool>(res_); }, "Requirement expressions must be convertible to bool (for now)");
380};
381
383template <StaticString OpStr, typename T>
385
387template <StaticString OpStr, typename T, typename U>
390
391} // namespace asmgrader
Definition requirement.hpp:214
Requirement(Op op)
Definition requirement.hpp:224
bool get_res() const
Definition requirement.hpp:249
Requirement(Op op, std::string description)
Definition requirement.hpp:242
Requirement(const DecomposedExpr< OpStr, Ts... > &decomposed_expr, const inspection::Tokenizer<> &tokens, std::string description)
Definition requirement.hpp:238
Requirement(const DecomposedExpr< OpStr, Ts... > &decomposed_expr, const inspection::Tokenizer<> &tokens)
Definition requirement.hpp:234
static constexpr auto default_description
Definition requirement.hpp:216
std::string get_description() const
Definition requirement.hpp:247
exprs::ExpressionRepr get_expr_repr() const
Definition requirement.hpp:252
A fully compile-time capable string type Guaranteed to be null-terminated.
Definition static_string.hpp:59
Definition expression_inspection.hpp:1670
constexpr Tokenizer subseq(std::size_t start, std::size_t len) const
Definition expression_inspection.hpp:1679
constexpr auto size() const
Definition expression_inspection.hpp:1698
Definition concepts.hpp:12
Definition requirement.hpp:112
Basic decomposition implementation, intended for use with the REQUIRE macro Inspired by Catch2,...
#define UNIMPLEMENTED(msg,...)
For features that have not yet / are not planned to be implemented, so that I don't bang my head agai...
Definition logging.hpp:49
boost::mp11::mp_list< std::pair< MapKeyT<"==">, Equal< T, U > >, std::pair< MapKeyT<"!=">, NotEqual< T, U > >, std::pair< MapKeyT<"<">, Less< T, U > >, std::pair< MapKeyT<"<=">, LessEqual< T, U > >, std::pair< MapKeyT<">">, Greater< T, U > >, std::pair< MapKeyT<">=">, GreaterEqual< T, U > > > StrToOpTMap
Definition requirement.hpp:132
ArityKind
Definition requirement.hpp:57
constexpr auto make(std::array< inspection::Tokenizer<>, sizeof...(Args)> arg_tokens, Args &&... args)
For argument deduction purposes.
Definition requirement.hpp:83
boost::mp11::mp_second< boost::mp11::mp_map_find< detail::StrToOpTMap< T, U >, detail::MapKeyT< OpStr > > > OpStrToType
Definition requirement.hpp:146
constexpr const auto & str
str customization point object The return value of this function is guaranteed to be parsable by high...
Definition stringize.hpp:190
constexpr const auto & repr
repr customization point object The return value of this function is guaranteed to be parsable by hig...
Definition stringize.hpp:178
Definition asm_buffer.hpp:20
Requirement(DecomposedExpr< OpStr, T > &&, const inspection::Tokenizer<> &, std::string) -> Requirement< exprs::Noop< T > >
Deduction guide for a single type decomposition expr.
Definition byte_array.hpp:94
See asmgrader::stringize.
Stores references to rhs and lhs of a comparison expression, serving as an interface to the "decompos...
Definition decomposer.hpp:29
std::tuple< Types &&... > operands
Definition decomposer.hpp:33
The str field is the representation of the operator (e.g., '+', '!=').
Definition requirement.hpp:197
std::vector< Expression > operands
List of operands. Operator arity = operands.size() Probably will never support ops with higher arity ...
Definition requirement.hpp:202
Repr repr
Definition requirement.hpp:198
Definition requirement.hpp:156
std::vector< asmgrader::inspection::Token > TokenStream
Definition requirement.hpp:166
boost::typeindex::type_index type_index
This field will probably never be used, but it might be nice later for debug info.
Definition requirement.hpp:171
Type kind
Definition requirement.hpp:175
Type
A heuristic of what type the value is. May not always be perfectly accurate.
Definition requirement.hpp:174
TokenStream raw_str_tokens
Tokenized raw_str see Tokenizer.
Definition requirement.hpp:168
bool is_literal
Whether the expression is a literal value, or is composed of solely literal values....
Definition requirement.hpp:182
stringize::StringizeResult repr
Representation of the expression as by repr
Definition requirement.hpp:158
stringize::StringizeResult str
Representation of the expression's value as by str
Definition requirement.hpp:161
std::string_view raw_str
Original string (should be as generated by the # prepocessor operator)
Definition requirement.hpp:164
Definition requirement.hpp:186
Repr repr
Definition requirement.hpp:187
Representation of an expression with all components stringized.
Definition requirement.hpp:152
Expression expression
Definition requirement.hpp:205
std::variant< Value, Operator > Expression
Definition requirement.hpp:193
Definition requirement.hpp:61
std::array< inspection::Tokenizer<>, sizeof...(Args)> arg_tokens
Definition requirement.hpp:69
std::tuple< Args... > args
Definition requirement.hpp:68
static constexpr ArityKind arity
Definition requirement.hpp:78
static constexpr std::string_view raw_str
Definition requirement.hpp:76
constexpr NAryOp(std::tuple< Args... > args_, std::array< inspection::Tokenizer<>, sizeof...(Args)> arg_tokens_)
Definition requirement.hpp:63
std::decay_t< std::invoke_result_t< OpFn, Args... > > EvalResT
Definition requirement.hpp:71
EvalResT eval() const
Definition requirement.hpp:74
Definition requirement.hpp:128
@ Identifier
https://en.cppreference.com/w/cpp/language/identifiers.html
Result type of a call to str or repr
Definition stringize.hpp:39