You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

234 lines
6.5 KiB

#pragma once
#include <charconv>
#include "parser.hpp"
namespace devilution {
class FieldsInRecordRange {
GetFieldResult *state_;
const char *const end_;
public:
FieldsInRecordRange(GetFieldResult *state, const char *end)
: state_(state)
, end_(end)
{
}
class Iterator {
GetFieldResult *state_;
const char *const end_;
public:
using iterator_category = std::input_iterator_tag;
using value_type = std::string_view;
Iterator()
: state_(nullptr)
, end_(nullptr)
{
}
Iterator(GetFieldResult *state, const char *end)
: state_(state)
, end_(end)
{
state_->status = GetFieldResult::Status::ReadyToRead;
}
[[nodiscard]] bool operator==(const Iterator &rhs) const
{
if (state_ == nullptr && rhs.state_ == nullptr)
return true;
return state_ != nullptr && rhs.state_ != nullptr && state_->next == rhs.state_->next;
}
[[nodiscard]] bool operator!=(const Iterator &rhs) const
{
return !(*this == rhs);
}
/**
* Advances to the next field in the current record
*/
Iterator &operator++()
{
return *this += 1;
}
/**
* @brief Advances by the specified number of fields
*
* if a non-zero increment is provided and advancing the iterator causes it to reach the end
* of the record the iterator is invalidated. It will compare equal to an end iterator and
* cannot be used for value access or any further parsing
* @param increment how many fields to advance (can be 0)
* @return self-reference
*/
Iterator &operator+=(unsigned increment)
{
if (state_->status == GetFieldResult::Status::ReadyToRead)
*state_ = DiscardMultipleFields(state_->next, end_, increment);
if (state_->endOfRecord()) {
state_ = nullptr;
} else {
// We use Status::ReadyToRead as a marker so we only read the next value on the next call to operator*
// this allows consumers to read from the input stream without using operator* if they want
state_->status = GetFieldResult::Status::ReadyToRead;
}
return *this;
}
/**
* @brief Returns a view of the current field
*
* This method scans the current field if this is the first value access since the last
* advance. You can repeatedly call operator* safely but you must not try to use parseInt
* after calling this method. If you calling parseInt and get an invalid_argument result
* back you can use this method to get the actual field value, but if you received any
* other result then parseInt has consumed the field and this method is no longer reliable.
* @return The current field value (may be an empty string) or a zero length string_view
*/
[[nodiscard]] value_type operator*()
{
if (state_->status == GetFieldResult::Status::ReadyToRead) {
*state_ = GetNextField(state_->next, end_);
}
return state_->value;
}
/**
* @brief Attempts to parse a field as a numeric value using std::from_chars
*
* This method is NOT repeatable. In addition to the behaviour of std::from_chars please
* heed the following warnings. If the field contains a value larger than will fit into the
* given type parsing will fail with std::errc::out_of_range and the field will be
* discarded. If a field starts with some digits then is followed by other characters the
* remainder will also be discarded. You can only get a useful value out of operator* if
* the result code is std::errc::invalid_argument.
* @tparam T an Integral type supported by std::from_chars
* @param destination value to store the result of successful parsing
* @return the error code from std::from_chars
*/
template <typename T>
[[nodiscard]] std::errc parseInt(T &destination)
{
auto result = std::from_chars(state_->next, end_, destination);
if (result.ec != std::errc::invalid_argument) {
// from_chars was able to consume at least one character, so discard the rest of the field
*state_ = DiscardField(result.ptr, end_);
}
return result.ec;
}
/**
* @brief Checks if this field is the last field in a file, not just in the record
*
* If you call this method before calling parseInt or operator* then this will trigger a
* read, meaning you can no longer use parseInt to try extract a numeric value. You
* probably want to use this after calling parseInt.
* @return true if this field is the last field in the file/RecordsRange
*/
[[nodiscard]] bool endOfFile() const
{
if (state_->status == GetFieldResult::Status::ReadyToRead) {
*state_ = GetNextField(state_->next, end_);
}
return state_->endOfFile();
}
};
[[nodiscard]] Iterator begin() const
{
return { state_, end_ };
}
[[nodiscard]] Iterator end() const
{
return {};
}
};
class RecordsRange {
const char *const begin_;
const char *const end_;
public:
RecordsRange(std::string_view characters)
: begin_(characters.data())
, end_(characters.data() + characters.size())
{
}
class Iterator {
GetFieldResult state_;
const char *const end_;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = FieldsInRecordRange;
Iterator()
: state_(nullptr, GetFieldResult::Status::EndOfFile)
, end_(nullptr)
{
}
Iterator(const char *begin, const char *end)
: state_(begin)
, end_(end)
{
}
[[nodiscard]] bool operator==(const Iterator &rhs) const
{
return state_.next == rhs.state_.next;
}
[[nodiscard]] bool operator!=(const Iterator &rhs) const
{
return !(*this == rhs);
}
Iterator &operator++()
{
return *this += 1;
}
Iterator &operator+=(unsigned increment)
{
if (!state_.endOfRecord())
state_ = DiscardRemainingFields(state_.next, end_);
if (state_.endOfFile()) {
state_.next = nullptr;
} else {
if (increment > 0)
state_ = DiscardMultipleRecords(state_.next, end_, increment - 1);
// We use Status::ReadyToRead as a marker in case the Field iterator is never
// used, so the next call to operator+= will advance past the current record
state_.status = GetFieldResult::Status::ReadyToRead;
}
return *this;
}
[[nodiscard]] value_type operator*()
{
return { &state_, end_ };
}
};
[[nodiscard]] Iterator begin() const
{
return { begin_, end_ };
}
[[nodiscard]] Iterator end() const
{
return {};
}
};
} // namespace devilution