mimic++ v6
Loading...
Searching...
No Matches
Sequence.hpp
Go to the documentation of this file.
1// Copyright Dominic (DNKpp) Koepke 2024 - 2025.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at
4// https://www.boost.org/LICENSE_1_0.txt)
5
6#ifndef MIMICPP_SEQUENCE_HPP
7#define MIMICPP_SEQUENCE_HPP
8
9#pragma once
10
11#include "mimic++/Fwd.hpp"
12#include "mimic++/Printer.hpp"
13#include "mimic++/Reporter.hpp"
14#include "mimic++/Utility.hpp"
15
16#include <algorithm>
17#include <array>
18#include <cassert>
19#include <functional>
20#include <span>
21#include <tuple>
22
23namespace mimicpp::sequence
24{
76
77 namespace detail
78 {
79 template <typename Id, auto priorityStrategy>
80 requires std::is_enum_v<Id>
81 && std::signed_integral<std::underlying_type_t<Id>>
82 && std::convertible_to<
83 std::invoke_result_t<decltype(priorityStrategy), Id, int>,
84 int>
85 class BasicSequence
86 {
87 public:
88 using IdT = Id;
89
90 ~BasicSequence() noexcept(false)
91 {
92 const auto iter = std::ranges::find_if_not(
93 m_Entries.cbegin() + m_Cursor,
94 m_Entries.cend(),
95 [](const State s) noexcept {
96 return s == State::satisfied
97 || s == State::saturated;
98 });
99
100 if (iter != m_Entries.cend())
101 {
102 mimicpp::detail::report_error(
103 format::format(
104 "Unfulfilled sequence. {} out of {} expectation(s) are satisfied.",
105 std::ranges::distance(m_Entries.cbegin(), iter),
106 m_Entries.size()));
107 }
108 }
109
110 [[nodiscard]]
111 BasicSequence() = default;
112
113 BasicSequence(const BasicSequence&) = delete;
114 BasicSequence& operator=(const BasicSequence&) = delete;
115 BasicSequence(BasicSequence&&) = delete;
116 BasicSequence& operator=(BasicSequence&&) = delete;
117
118 [[nodiscard]]
119 constexpr std::optional<int> priority_of(const IdT id) const noexcept
120 {
121 assert(is_valid(id));
122
123 if (is_consumable(id))
124 {
125 return std::invoke(
126 priorityStrategy,
127 id,
128 m_Cursor);
129 }
130
131 return std::nullopt;
132 }
133
134 constexpr void set_satisfied(const IdT id) noexcept
135 {
136 assert(is_valid(id));
137 assert(m_Cursor <= to_underlying(id));
138
139 auto& element = m_Entries[to_underlying(id)];
140 assert(element == State::unsatisfied);
141 element = State::satisfied;
142 }
143
144 constexpr void set_saturated(const IdT id) noexcept
145 {
146 assert(is_valid(id));
147 const auto index = to_underlying(id);
148 assert(m_Cursor <= index);
149
150 auto& element = m_Entries[index];
151 assert(
152 element == State::unsatisfied
153 || element == State::satisfied);
154 element = State::saturated;
155 }
156
157 [[nodiscard]]
158 constexpr bool is_consumable(const IdT id) const noexcept
159 {
160 assert(is_valid(id));
161
162 const int index = to_underlying(id);
163 const auto state = m_Entries[index];
164 return m_Cursor <= index
165 && std::ranges::all_of(
166 m_Entries.begin() + m_Cursor,
167 m_Entries.begin() + index,
168 [](const State s) noexcept {
169 return s == State::satisfied
170 || s == State::saturated;
171 })
172 && (state == State::unsatisfied
173 || state == State::satisfied);
174 }
175
176 constexpr void consume(const IdT id) noexcept
177 {
178 assert(is_consumable(id));
179
180 m_Cursor = to_underlying(id);
181 }
182
183 [[nodiscard]]
184 constexpr IdT add()
185 {
186 if (!std::in_range<std::underlying_type_t<IdT>>(m_Entries.size()))
187 [[unlikely]]
188 {
189 throw std::runtime_error{
190 "Sequence already holds maximum amount of elements."};
191 }
192
193 m_Entries.emplace_back(State::unsatisfied);
194 return static_cast<IdT>(m_Entries.size() - 1);
195 }
196
197 [[nodiscard]]
198 constexpr Tag tag() const noexcept
199 {
200 return Tag{
201 std::bit_cast<std::ptrdiff_t>(this)};
202 }
203
204 private:
205 enum class State
206 {
207 unsatisfied,
208 satisfied,
209 saturated
210 };
211
212 std::vector<State> m_Entries{};
213 int m_Cursor{};
214
215 [[nodiscard]]
216 constexpr bool is_valid(const IdT id) const noexcept
217 {
218 return 0 <= to_underlying(id)
219 && std::cmp_less(to_underlying(id), m_Entries.size());
220 }
221 };
222
223 class LazyStrategy
224 {
225 public:
226 [[nodiscard]]
227 constexpr int operator()(const auto id, const int cursor) const noexcept
228 {
229 const auto index = to_underlying(id);
230 assert(std::cmp_less_equal(cursor, index));
231
232 return std::numeric_limits<int>::max()
233 - (static_cast<int>(index) - cursor);
234 }
235 };
236
237 class GreedyStrategy
238 {
239 public:
240 [[nodiscard]]
241 constexpr int operator()(const auto id, const int cursor) const noexcept
242 {
243 const auto index = to_underlying(id);
244 assert(std::cmp_less_equal(cursor, index));
245
246 return static_cast<int>(index) - cursor;
247 }
248 };
249
250 template <typename Id, auto priorityStrategy>
251 class BasicSequenceInterface
252 {
253 template <typename... Sequences>
254 friend class Config;
255
256 public:
257 using SequenceT = BasicSequence<Id, priorityStrategy>;
258
259 ~BasicSequenceInterface() = default;
260
261 [[nodiscard]]
262 BasicSequenceInterface() = default;
263
264 BasicSequenceInterface(const BasicSequenceInterface&) = delete;
265 BasicSequenceInterface& operator=(const BasicSequenceInterface&) = delete;
266 BasicSequenceInterface(BasicSequenceInterface&&) = delete;
267 BasicSequenceInterface& operator=(BasicSequenceInterface&&) = delete;
268
269 [[nodiscard]]
270 constexpr Tag tag() const noexcept
271 {
272 return m_Sequence->tag();
273 }
274
275 private:
276 std::shared_ptr<SequenceT> m_Sequence{
277 std::make_shared<SequenceT>()};
278 };
279
280 template <typename... Sequences>
281 class Config
282 {
283 template <typename... Ts>
284 friend class Config;
285
286 public:
287 static constexpr std::size_t sequenceCount = sizeof...(Sequences);
288
289 [[nodiscard]]
290 Config()
291 requires(0 == sizeof...(Sequences))
292 = default;
293
294 template <typename... Interfaces>
295 requires(sizeof...(Interfaces) == sequenceCount)
296 [[nodiscard]] explicit constexpr Config(Interfaces&... interfaces) noexcept(1 == sequenceCount)
297 : Config{
298 interfaces.m_Sequence...}
299 {
300 }
301
302 [[nodiscard]]
303 constexpr auto& sequences() const noexcept
304 {
305 return m_Sequences;
306 }
307
308 template <typename... OtherSequences>
309 [[nodiscard]]
310 constexpr Config<Sequences..., OtherSequences...> concat(
311 const Config<OtherSequences...>& other) const
312 {
313 return std::apply(
314 [](auto... sequences) {
315 return Config<Sequences..., OtherSequences...>{
316 std::move(sequences)...};
317 },
318 std::tuple_cat(m_Sequences, other.sequences()));
319 }
320
321 private:
322 std::tuple<std::shared_ptr<Sequences>...> m_Sequences;
323
324 [[nodiscard]]
325 explicit constexpr Config(std::shared_ptr<Sequences>... sequences) noexcept(1 == sequenceCount)
326 requires(0 < sequenceCount)
327 {
328 if constexpr (1 < sequenceCount)
329 {
330 std::array tags{
331 sequences->tag()...};
332
333 std::ranges::sort(tags);
334 if (!std::ranges::empty(std::ranges::unique(tags)))
335 {
336 throw std::invalid_argument{
337 "Expectations can not be assigned to the same sequence multiple times."};
338 }
339 }
340
341 m_Sequences = std::tuple{
342 std::move(sequences)...};
343 }
344 };
345 }
346}
347
348namespace mimicpp
349{
359 : public sequence::detail::BasicSequenceInterface<
360 sequence::Id,
361 sequence::detail::LazyStrategy{}>
362 {
363 };
364
374 : public sequence::detail::BasicSequenceInterface<
375 sequence::Id,
376 sequence::detail::GreedyStrategy{}>
377 {
378 };
379
385}
386
387namespace mimicpp::sequence::detail
388{
389 [[nodiscard]]
390 constexpr bool has_better_rating(
391 const std::span<const rating> lhs,
392 const std::span<const rating> rhs) noexcept
393 {
394 int rating{};
395 for (const auto& [lhsPriority, lhsTag] : lhs)
396 {
397 if (const auto iter = std::ranges::find(rhs, lhsTag, &rating::tag);
398 iter != std::ranges::end(rhs))
399 {
400 rating += lhsPriority < iter->priority
401 ? -1
402 : 1;
403 }
404 else
405 {
406 return true;
407 }
408 }
409
410 return 0 <= rating;
411 }
412}
413
414namespace mimicpp::expect
415{
425 template <typename Id, auto priorityStrategy>
426 [[nodiscard]]
427 constexpr auto in_sequence(sequence::detail::BasicSequenceInterface<Id, priorityStrategy>& sequence) noexcept
428 {
429 using ConfigT = sequence::detail::Config<
430 sequence::detail::BasicSequence<Id, priorityStrategy>>;
431
432 return ConfigT{
433 sequence};
434 }
435
446 template <
447 typename FirstId,
448 auto firstPriorityStrategy,
449 typename SecondId,
450 auto secondPriorityStrategy,
451 typename... OtherIds,
452 auto... otherPriorityStrategies>
453 [[nodiscard]]
454 constexpr auto in_sequences(
455 sequence::detail::BasicSequenceInterface<FirstId, firstPriorityStrategy>& firstSequence,
456 sequence::detail::BasicSequenceInterface<SecondId, secondPriorityStrategy>& secondSequence,
457 sequence::detail::BasicSequenceInterface<OtherIds, otherPriorityStrategies>&... otherSequences)
458 {
459 using ConfigT = sequence::detail::Config<
460 sequence::detail::BasicSequence<FirstId, firstPriorityStrategy>,
461 sequence::detail::BasicSequence<SecondId, secondPriorityStrategy>,
462 sequence::detail::BasicSequence<OtherIds, otherPriorityStrategies>...>;
463
464 return ConfigT{
465 firstSequence,
466 secondSequence,
467 otherSequences...};
468 }
469}
470
471#endif
The greedy sequence interface.
Definition Sequence.hpp:377
The lazy sequence interface.
Definition Sequence.hpp:362
constexpr auto in_sequences(sequence::detail::BasicSequenceInterface< FirstId, firstPriorityStrategy > &firstSequence, sequence::detail::BasicSequenceInterface< SecondId, secondPriorityStrategy > &secondSequence, sequence::detail::BasicSequenceInterface< OtherIds, otherPriorityStrategies > &... otherSequences)
Attaches the expectation onto the listed sequences.
Definition Sequence.hpp:454
constexpr auto in_sequence(sequence::detail::BasicSequenceInterface< Id, priorityStrategy > &sequence) noexcept
Attaches the expectation onto a sequence.
Definition Sequence.hpp:427
LazySequence SequenceT
The default sequence type (LazySequence).
Definition Sequence.hpp:384
Definition ArgRequirementPolicies.hpp:106
Definition Fwd.hpp:353
Tag
Definition Fwd.hpp:355
Id
Definition Fwd.hpp:359
Definition BoostTest.hpp:20
constexpr std::underlying_type_t< T > to_underlying(const T value) noexcept
Definition Utility.hpp:71
Tag tag
Definition Fwd.hpp:365