mimic++ v9.2.1
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"
20
21#ifndef MIMICPP_DETAIL_IS_MODULE
22 #include <algorithm>
23 #include <array>
24 #include <functional>
25 #include <span>
26 #include <tuple>
27#endif
28
29namespace mimicpp::sequence
30{
86}
87
88namespace mimicpp::sequence::detail
89{
90 template <typename Id, auto priorityStrategy>
91 requires std::is_enum_v<Id>
92 && std::signed_integral<std::underlying_type_t<Id>>
93 && std::convertible_to<
94 std::invoke_result_t<decltype(priorityStrategy), Id, int>,
95 int>
96 class BasicSequence
97 {
98 public:
99 BasicSequence(BasicSequence const&) = delete;
100 BasicSequence& operator=(BasicSequence const&) = delete;
101 BasicSequence(BasicSequence&&) = delete;
102 BasicSequence& operator=(BasicSequence&&) = delete;
103
104 ~BasicSequence() noexcept(false)
105 {
106 auto const iter = std::ranges::find_if_not(m_Entries, &Entry::is_fulfilled);
107 auto const satisfiedCount = std::ranges::distance(m_Entries.cbegin(), iter);
108 MIMICPP_ASSERT(m_Cursor <= satisfiedCount, "Cursor skipped unsatisfied entries.");
109
110 if (iter != m_Entries.cend())
111 {
112 reporting::detail::report_error(
113 format::format(
114 "Unfulfilled sequence. {} out of {} expectation(s) are satisfied.",
115 satisfiedCount,
116 m_Entries.size()));
117 }
118 }
119
120 [[nodiscard]]
121 explicit constexpr BasicSequence(util::SourceLocation loc = {}) noexcept
122 : m_Loc{std::move(loc)}
123 {
124 }
125
126 [[nodiscard]]
127 constexpr util::SourceLocation const& from() const noexcept
128 {
129 return m_Loc;
130 }
131
132 [[nodiscard]]
133 constexpr std::optional<util::SourceLocation> head_from() const
134 {
135 auto const pending = std::span{m_Entries}.subspan(m_Cursor);
136 auto const iter = std::ranges::find_if(pending, &Entry::is_active);
137 if (iter != pending.end())
138 {
139 return iter->loc;
140 }
141
142 return std::nullopt;
143 }
144
145 [[nodiscard]]
146 constexpr std::optional<int> priority_of(Id const id) const noexcept
147 {
148 MIMICPP_ASSERT(is_valid(id), "Invalid id given.");
149
150 if (is_consumable(id))
151 {
152 return std::invoke(priorityStrategy, id, m_Cursor);
153 }
154
155 return std::nullopt;
156 }
157
158 constexpr void set_satisfied(Id const id) noexcept
159 {
160 MIMICPP_ASSERT(is_valid(id), "Invalid id given.");
161 MIMICPP_ASSERT(m_Cursor <= util::to_underlying(id), "Invalid state.");
162
163 auto& element = m_Entries[util::to_underlying(id)];
164 MIMICPP_ASSERT(element.is_unsatisfied(), "Element is in unexpected state.");
165 element.state = State::satisfied;
166 }
167
168 constexpr void set_saturated(Id const id) noexcept
169 {
170 MIMICPP_ASSERT(is_valid(id), "Invalid id given.");
171 int const index = util::to_underlying(id);
172 MIMICPP_ASSERT(m_Cursor <= index, "Invalid state.");
173
174 auto& element = m_Entries[index];
175 MIMICPP_ASSERT(element.is_active(), "Element is in unexpected state.");
176 element.state = State::saturated;
177 }
178
179 [[nodiscard]]
180 constexpr bool is_consumable(Id const id) const noexcept
181 {
182 MIMICPP_ASSERT(is_valid(id), "Invalid id given.");
183
184 std::span const pending = std::span{m_Entries}.subspan(m_Cursor);
185 auto const index = util::to_underlying(id) - m_Cursor;
186
187 return 0 <= index
188 && std::ranges::all_of(pending.first(index), &Entry::is_fulfilled)
189 && pending[index].is_active();
190 }
191
192 constexpr void consume(Id const id) noexcept
193 {
194 MIMICPP_ASSERT(is_consumable(id), "Sequence is not in consumable state.");
195
196 m_Cursor = util::to_underlying(id);
197 }
198
199 [[nodiscard]]
200 constexpr Id add(util::SourceLocation info)
201 {
202 if (!std::in_range<std::underlying_type_t<Id>>(m_Entries.size()))
203 [[unlikely]]
204 {
205 throw std::runtime_error{"Sequence already holds maximum amount of elements."};
206 }
207
208 m_Entries.emplace_back(State::unsatisfied, std::move(info));
209
210 return static_cast<Id>(m_Entries.size() - 1);
211 }
212
213 [[nodiscard]]
214 constexpr Tag tag() const noexcept
215 {
217 }
218
219 private:
220 util::SourceLocation m_Loc;
221
222 enum class State
223 {
224 unsatisfied,
225 satisfied,
226 saturated
227 };
228
229 struct Entry
230 {
231 State state{State::unsatisfied};
232 util::SourceLocation loc{};
233
234 [[nodiscard]]
235 constexpr bool is_fulfilled() const noexcept
236 {
237 return state == State::satisfied
238 || state == State::saturated;
239 }
240
241 [[nodiscard]]
242 constexpr bool is_active() const noexcept
243 {
244 return state == State::unsatisfied
245 || state == State::satisfied;
246 }
247
248 [[nodiscard]]
249 constexpr bool is_unsatisfied() const noexcept
250 {
251 return state == State::unsatisfied;
252 }
253 };
254
255 std::vector<Entry> m_Entries{};
256 int m_Cursor{};
257
258 [[nodiscard]]
259 constexpr bool is_valid(Id const id) const noexcept
260 {
261 auto const index = util::to_underlying(id);
262
263 return 0 <= index
264 && index < std::ssize(m_Entries);
265 }
266 };
267
268 class LazyStrategy
269 {
270 public:
271 [[nodiscard]]
272 constexpr int operator()(auto const id, int const cursor) const noexcept
273 {
274 int const index = util::to_underlying(id);
275 MIMICPP_ASSERT(std::cmp_less_equal(cursor, index), "Invalid state.");
276
277 return std::numeric_limits<int>::max()
278 - (index - cursor);
279 }
280 };
281
282 class GreedyStrategy
283 {
284 public:
285 [[nodiscard]]
286 constexpr int operator()(auto const id, int const cursor) const noexcept
287 {
288 int const index = util::to_underlying(id);
289 MIMICPP_ASSERT(std::cmp_less_equal(cursor, index), "Invalid state.");
290
291 return index - cursor;
292 }
293 };
294
295 template <typename... Sequences>
296 class Config;
297
298 template <typename Id, auto priorityStrategy>
299 class BasicSequenceInterface
300 {
301 using Sequence = BasicSequence<Id, priorityStrategy>;
302
303 public:
304 BasicSequenceInterface(const BasicSequenceInterface&) = delete;
305 BasicSequenceInterface& operator=(const BasicSequenceInterface&) = delete;
306
307 ~BasicSequenceInterface() = default;
308
309 [[nodiscard]]
310 explicit BasicSequenceInterface(util::SourceLocation loc = {})
311 : m_Sequence{std::make_shared<Sequence>(std::move(loc))}
312 {
313 }
314
315 [[nodiscard]]
316 BasicSequenceInterface(BasicSequenceInterface&&) = default;
317 BasicSequenceInterface& operator=(BasicSequenceInterface&&) = default;
318
319 [[nodiscard]]
320 constexpr Tag tag() const noexcept
321 {
322 return m_Sequence->tag();
323 }
324
325 [[nodiscard]]
326 util::SourceLocation const& from() const noexcept
327 {
328 return m_Sequence->from();
329 }
330
331 [[nodiscard]]
332 std::optional<util::SourceLocation> head_from() const
333 {
334 return m_Sequence->head_from();
335 }
336
337 template <typename... Sequences>
338 requires util::same_as_any<Sequence, Sequences...>
339 constexpr std::shared_ptr<Sequence> sequence([[maybe_unused]] util::pass_key<Config<Sequences...>> key) const
340 {
341 return m_Sequence;
342 }
343
344 private:
345 std::shared_ptr<Sequence> m_Sequence;
346 };
347
348 template <typename... Sequences>
349 class Config
350 {
351 static constexpr util::pass_key<Config> selfKey{};
352
353 public:
354 static constexpr std::size_t sequenceCount = sizeof...(Sequences);
355
356 [[nodiscard]]
357 Config()
358 requires(0u == sequenceCount)
359 = default;
360
361 template <typename First, typename... Others>
362 requires(1u + sizeof...(Others) == sequenceCount)
363 && (!std::same_as<util::pass_key<Config>, std::remove_cvref_t<First>>)
364 && (!std::same_as<Config, std::remove_cvref_t<First>>)
365 [[nodiscard]] //
366 explicit constexpr Config(First& firstInterface, Others&... interfaces) noexcept(1u == sequenceCount)
367 : Config{
368 selfKey,
369 firstInterface.sequence(selfKey),
370 interfaces.sequence(selfKey)...}
371 {
372 }
373
374 template <typename... OtherSequences>
375 [[nodiscard]] //
376 explicit constexpr Config(
377 [[maybe_unused]] util::pass_key<Config<OtherSequences...>> const key,
378 std::shared_ptr<Sequences>... sequences)
379 noexcept(1u == sequenceCount)
380 {
381 if constexpr (1u < sequenceCount)
382 {
383 std::array tags{sequences->tag()...};
384
385 std::ranges::sort(tags);
386 if (!std::ranges::empty(std::ranges::unique(tags)))
387 {
388 throw std::invalid_argument{
389 "Expectations can not be assigned to the same sequence multiple times."};
390 }
391 }
392
393 m_Sequences = std::tuple{std::move(sequences)...};
394 }
395
396 [[nodiscard]]
397 constexpr auto& sequences() const noexcept
398 {
399 return m_Sequences;
400 }
401
402 template <typename... OtherSequences>
403 [[nodiscard]]
404 constexpr Config<Sequences..., OtherSequences...> concat(Config<OtherSequences...> const& other) const
405 {
406 return std::make_from_tuple<Config<Sequences..., OtherSequences...>>(
407 std::tuple_cat(std::make_tuple(selfKey), m_Sequences, other.sequences()));
408 }
409
410 private:
411 std::tuple<std::shared_ptr<Sequences>...> m_Sequences;
412 };
413}
414
416{
426 : public sequence::detail::BasicSequenceInterface<
427 sequence::Id,
428 sequence::detail::LazyStrategy{}>
429 {
430 public:
431 [[nodiscard]]
432 explicit LazySequence([[maybe_unused]] auto&&... canary, util::SourceLocation loc = {})
433 : BasicSequenceInterface{std::move(loc)}
434 {
435 static_assert(0u == sizeof...(canary), "LazySequence does not accept constructor arguments.");
436 }
437 };
438
448 : public sequence::detail::BasicSequenceInterface<
449 sequence::Id,
450 sequence::detail::GreedyStrategy{}>
451 {
452 public:
453 [[nodiscard]]
454 explicit GreedySequence([[maybe_unused]] auto&&... canary, util::SourceLocation loc = {})
455 : BasicSequenceInterface{std::move(loc)}
456 {
457 static_assert(0u == sizeof...(canary), "GreedySequence does not accept constructor arguments.");
458 }
459 };
460
466 using SequenceT [[deprecated("Please use mimicpp::Sequence instead.")]] = LazySequence;
467
473}
474
475namespace mimicpp::sequence::detail
476{
477 [[nodiscard]]
478 constexpr bool has_better_rating(
479 std::span<rating const> const lhs,
480 std::span<rating const> const rhs) noexcept
481 {
482 int rating{};
483 for (auto const& [lhsPriority, lhsTag] : lhs)
484 {
485 if (auto const iter = std::ranges::find(rhs, lhsTag, &rating::tag);
486 iter != std::ranges::end(rhs))
487 {
488 rating += lhsPriority < iter->priority ? -1 : 1;
489 }
490 else
491 {
492 return true;
493 }
494 }
495
496 return 0 <= rating;
497 }
498}
499
501{
511 template <typename Id, auto priorityStrategy>
512 [[nodiscard]]
513 constexpr auto in_sequence(sequence::detail::BasicSequenceInterface<Id, priorityStrategy>& sequence)
514 {
515 using Config = sequence::detail::Config<
516 sequence::detail::BasicSequence<Id, priorityStrategy>>;
517
518 return Config{sequence};
519 }
520
531 template <
532 typename FirstId,
533 auto firstPriorityStrategy,
534 typename SecondId,
535 auto secondPriorityStrategy,
536 typename... OtherIds,
537 auto... otherPriorityStrategies>
538 [[nodiscard]]
539 constexpr auto in_sequences(
540 sequence::detail::BasicSequenceInterface<FirstId, firstPriorityStrategy>& firstSequence,
541 sequence::detail::BasicSequenceInterface<SecondId, secondPriorityStrategy>& secondSequence,
542 sequence::detail::BasicSequenceInterface<OtherIds, otherPriorityStrategies>&... otherSequences)
543 {
544 using Config = sequence::detail::Config<
545 sequence::detail::BasicSequence<FirstId, firstPriorityStrategy>,
546 sequence::detail::BasicSequence<SecondId, secondPriorityStrategy>,
547 sequence::detail::BasicSequence<OtherIds, otherPriorityStrategies>...>;
548
549 return Config{firstSequence, secondSequence, otherSequences...};
550 }
551}
552
553#endif
#define MIMICPP_ASSERT(condition, msg)
Definition Config.hpp:51
#define MIMICPP_DETAIL_MODULE_EXPORT
Definition Config.hpp:19
GreedySequence(auto &&... canary, util::SourceLocation loc={})
Definition Sequence.hpp:454
The lazy sequence interface.
Definition Sequence.hpp:429
LazySequence(auto &&... canary, util::SourceLocation loc={})
Definition Sequence.hpp:432
A thin wrapper around general source-location info.
Definition SourceLocation.hpp:38
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:539
LazySequence Sequence
The default sequence type (LazySequence).
Definition Sequence.hpp:472
constexpr auto in_sequence(sequence::detail::BasicSequenceInterface< Id, priorityStrategy > &sequence)
Attaches the expectation onto a sequence.
Definition Sequence.hpp:513
Definition ArgRequirementPolicies.hpp:125
Definition Fwd.hpp:400
Tag
Definition Fwd.hpp:402
Id
Definition Fwd.hpp:406
constexpr std::underlying_type_t< T > to_underlying(T const value) noexcept
Definition C++23Backports.hpp:62
std::enable_if_t< sizeof(To)==sizeof(From) &&std::is_trivially_copyable_v< From > &&std::is_trivially_copyable_v< To >, To > bit_cast(From const &src) noexcept
Definition C++20Compatibility.hpp:45
Definition Call.hpp:24
Tag tag
Definition Fwd.hpp:412