srctree

Robin Linden parent 915e256c 7cd96bd2
util: Add a helper class for managing data you want to keep history for

inlinesplit
filename was Deleted added: 193, removed: 2, total 191
@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2022 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef UTIL_HISTORY_H_
#define UTIL_HISTORY_H_
 
#include <optional>
#include <utility>
#include <vector>
 
namespace util {
 
template<typename T>
class History {
public:
constexpr void push(T entry) {
// Are we already on this entry?
if (current_index_ >= 0 && entries_[current_index_] == entry) {
return;
}
 
current_index_ += 1;
 
// Does the entry already exist in the history where we want it to be?
if (static_cast<std::size_t>(current_index_) < entries_.size() && entries_[current_index_] == entry) {
return;
}
 
// Does the entry require more space in the list?
if (static_cast<std::size_t>(current_index_) == entries_.size()) {
entries_.push_back(std::move(entry));
return;
}
 
// This entry should go in the middle of the history. Add it and nuke everything after it.
entries_[current_index_] = std::move(entry);
entries_.erase(cbegin(entries_) + current_index_ + 1, cend(entries_));
}
 
constexpr std::optional<T> pop() {
if (current_index_ >= 0) {
return entries_[std::exchange(current_index_, current_index_ - 1)];
}
 
return std::nullopt;
}
 
constexpr std::optional<T> previous() const {
if (auto previous_index = current_index_ - 1; previous_index >= 0) {
return entries_[previous_index];
}
 
return std::nullopt;
}
 
constexpr std::optional<T> current() const {
if (current_index_ >= 0 && static_cast<std::size_t>(current_index_) < entries_.size()) {
return entries_[current_index_];
}
 
return std::nullopt;
}
 
constexpr std::optional<T> next() const {
if (auto next_index = static_cast<std::size_t>(current_index_ + 1); next_index < entries_.size()) {
return entries_[next_index];
}
 
return std::nullopt;
}
 
constexpr std::vector<T> const &entries() const { return entries_; }
 
private:
int current_index_{-1};
std::vector<T> entries_;
};
 
} // namespace util
 
#endif
 
filename was Deleted added: 193, removed: 2, total 191
@@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: 2022 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "util/history.h"
 
#include "etest/etest.h"
 
#include <optional>
#include <vector>
 
using etest::expect_eq;
using util::History;
 
int main() {
etest::test("no history", [] {
History<int> h;
expect_eq(h.current(), std::nullopt);
expect_eq(h.next(), std::nullopt);
expect_eq(h.previous(), std::nullopt);
expect_eq(h.pop(), std::nullopt);
});
 
etest::test("pushing", [] {
History<int> h;
 
h.push(1);
expect_eq(h.current(), 1);
expect_eq(h.next(), std::nullopt);
expect_eq(h.previous(), std::nullopt);
 
h.push(2);
expect_eq(h.current(), 2);
expect_eq(h.next(), std::nullopt);
expect_eq(h.previous(), 1);
});
 
etest::test("popping", [] {
History<int> h;
 
h.push(1);
h.push(2);
 
expect_eq(h.pop(), 2);
expect_eq(h.current(), 1);
expect_eq(h.next(), 2);
expect_eq(h.previous(), std::nullopt);
 
expect_eq(h.pop(), 1);
expect_eq(h.current(), std::nullopt);
expect_eq(h.next(), 1);
expect_eq(h.previous(), std::nullopt);
 
expect_eq(h.pop(), std::nullopt);
expect_eq(h.current(), std::nullopt);
expect_eq(h.next(), 1);
expect_eq(h.previous(), std::nullopt);
});
 
etest::test("rewriting history", [] {
History<int> h;
 
h.push(1);
h.push(2);
h.push(3);
h.push(4);
 
expect_eq(h.pop(), 4);
expect_eq(h.pop(), 3);
h.push(5);
 
expect_eq(h.current(), 5);
expect_eq(h.next(), std::nullopt);
expect_eq(h.previous(), 2);
expect_eq(h.entries(), std::vector<int>{1, 2, 5});
});
 
etest::test("duplicate entries aren't added", [] {
History<int> h;
 
h.push(1);
h.push(1);
 
expect_eq(h.current(), 1);
expect_eq(h.next(), std::nullopt);
expect_eq(h.previous(), std::nullopt);
expect_eq(h.entries(), std::vector<int>{1});
});
 
etest::test("pushing an entry already in history doesn't clear entries after it", [] {
History<int> h;
 
h.push(1);
h.push(2);
h.push(3);
h.push(4);
expect_eq(h.entries(), std::vector<int>{1, 2, 3, 4});
expect_eq(h.pop(), 4);
expect_eq(h.pop(), 3);
expect_eq(h.pop(), 2);
 
expect_eq(h.entries(), std::vector<int>{1, 2, 3, 4});
 
h.push(2);
expect_eq(h.entries(), std::vector<int>{1, 2, 3, 4});
});
 
return etest::run_all_tests();
}