12#ifndef STRING_CONSUMER_HPP
13#define STRING_CONSUMER_HPP
29 using size_type = std::string_view::size_type;
34 static constexpr size_type
npos = std::string_view::npos;
49 size_type position = 0;
51 static void LogError(std::string &&msg);
65 explicit StringConsumer(std::span<const char> src) : src(src.data(), src.size()) {}
70 [[nodiscard]]
bool AnyBytesLeft() const noexcept {
return this->position < this->src.size(); }
74 [[nodiscard]] size_type
GetBytesLeft() const noexcept {
return this->src.size() - this->position; }
79 [[nodiscard]]
bool AnyBytesRead() const noexcept {
return this->position > 0; }
83 [[nodiscard]] size_type
GetBytesRead() const noexcept {
return this->position; }
88 [[nodiscard]] std::string_view
GetOrigData() const noexcept {
return this->src; }
92 [[nodiscard]] std::string_view
GetReadData() const noexcept {
return this->src.substr(0, this->position); }
96 [[nodiscard]] std::string_view
GetLeftData() const noexcept {
return this->src.substr(this->position); }
101 void SkipAll() { this->position = this->src.size(); }
107 [[nodiscard]] std::optional<uint8_t>
PeekUint8()
const;
114 if (value.has_value()) this->
SkipUint8();
126 return value.value_or(def);
140 if (!result.has_value())
return std::nullopt;
141 return static_cast<int8_t
>(*result);
149 if (value.has_value()) this->
SkipSint8();
161 return value.value_or(def);
172 [[nodiscard]] std::optional<uint16_t>
PeekUint16LE()
const;
192 return value.value_or(def);
207 if (!result.has_value())
return std::nullopt;
208 return static_cast<int16_t
>(*result);
229 return value.value_or(def);
241 [[nodiscard]] std::optional<uint32_t>
PeekUint32LE()
const;
261 return value.value_or(def);
276 if (!result.has_value())
return std::nullopt;
277 return static_cast<int32_t
>(*result);
298 return value.value_or(def);
310 [[nodiscard]] std::optional<uint64_t>
PeekUint64LE()
const;
330 return value.value_or(def);
345 if (!result.has_value())
return std::nullopt;
346 return static_cast<int64_t
>(*result);
367 return value.value_or(def);
379 [[nodiscard]] std::optional<char>
PeekChar()
const;
386 if (value.has_value()) this->
SkipChar();
397 return value.value_or(def);
408 [[nodiscard]] std::pair<size_type, char32_t>
PeekUtf8()
const;
414 auto [len, value] = this->
PeekUtf8();
415 if (len == 0)
return std::nullopt;
425 [[nodiscard]]
char32_t ReadUtf8(
char32_t def =
'?')
427 auto [len, value] = this->
PeekUtf8();
428 this->
Skip(len > 0 ? len : 1);
429 return len > 0 ? value : def;
441 this->
Skip(len > 0 ? len : 1);
447 [[nodiscard]]
bool PeekIf(std::string_view str)
const
449 return this->src.compare(this->position, str.size(), str) == 0;
447 [[nodiscard]]
bool PeekIf(std::string_view str)
const {
…}
454 [[nodiscard]]
bool ReadIf(std::string_view str)
456 bool result = this->
PeekIf(str);
457 if (result) this->
Skip(str.size());
454 [[nodiscard]]
bool ReadIf(std::string_view str) {
…}
465 if (this->
PeekIf(str)) this->
Skip(str.size());
473 return this->
PeekIf({&c, 1});
480 return this->
ReadIf({&c, 1});
487 return this->
SkipIf({&c, 1});
495 auto [len, result] = this->
PeekUtf8();
496 return len > 0 && result == c;
503 auto [len, result] = this->
PeekUtf8();
504 if (len == 0 || result != c)
return false;
513 auto [len, result] = this->
PeekUtf8();
514 if (len > 0 && result == c) {
524 [[nodiscard]] std::string_view
Peek(size_type len)
const;
530 [[nodiscard]] std::string_view
Read(size_type len)
532 auto result = this->
Peek(len);
533 if (len !=
npos && len != result.size()) {
534 LogError(fmt::format(
"Source buffer too short: {} > {}", len, result.size()));
536 this->
Skip(result.size());
530 [[nodiscard]] std::string_view
Read(size_type len) {
…}
543 void Skip(size_type len);
549 [[nodiscard]] size_type
Find(std::string_view str)
const;
556 return this->
Find({&c, 1});
562 [[nodiscard]] size_type
FindUtf8(
char32_t c)
const;
568 [[nodiscard]] size_type
FindCharIn(std::string_view chars)
const;
573 [[nodiscard]] size_type
FindCharNotIn(std::string_view chars)
const;
579 [[nodiscard]] std::optional<char>
PeekCharIfIn(std::string_view chars)
const
581 assert(!chars.empty());
582 std::optional<char> c = this->
PeekChar();
583 if (c.has_value() && chars.find(*c) != std::string_view::npos)
return c;
579 [[nodiscard]] std::optional<char>
PeekCharIfIn(std::string_view chars)
const {
…}
593 if (result.has_value()) this->
Skip(1);
602 if (result.has_value()) this->
Skip(1);
611 assert(!chars.empty());
612 std::optional<char> c = this->
PeekChar();
613 if (c.has_value() && chars.find(*c) == std::string_view::npos)
return c;
623 if (result.has_value()) this->
Skip(1);
632 if (result.has_value()) this->
Skip(1);
642 return this->
Peek(len);
651 return this->
Read(len);
669 return this->
Peek(len);
678 return this->
Read(len);
713 assert(!str.empty());
715 this->
Skip(result.size());
723 while (this->
ReadIf(str)) {}
735 assert(!str.empty());
746 while (this->
ReadIf(str)) {}
800 [[nodiscard]]
static std::pair<size_type, T> ParseIntegerBase(std::string_view src,
int base,
bool clamp,
bool log_errors)
804 if (src.starts_with(
"0x") || src.starts_with(
"0X")) {
805 auto [len, value] = ParseIntegerBase<T>(src.substr(2), 16, clamp, log_errors);
806 if (len == 0)
return {};
807 return {len + 2, value};
811 if (std::is_signed_v<T> && (src.starts_with(
"-0x") || src.starts_with(
"-0X"))) {
812 using Unsigned = std::make_unsigned_t<T>;
813 auto [len, uvalue] = ParseIntegerBase<Unsigned>(src.substr(3), 16, clamp, log_errors);
814 if (len == 0)
return {};
815 T value =
static_cast<T
>(0 - uvalue);
818 if (log_errors) LogError(fmt::format(
"Integer out of range: '{}'", src.substr(0, len + 3)));
821 value = std::numeric_limits<T>::lowest();
823 return {len + 3, value};
827 return ParseIntegerBase<T>(src, 10, clamp, log_errors);
831 assert(base == 8 || base == 10 || base == 16);
832 auto result = std::from_chars(src.data(), src.data() + src.size(), value, base);
833 auto len = result.ptr - src.data();
834 if (result.ec == std::errc::result_out_of_range) {
836 if (log_errors) LogError(fmt::format(
"Integer out of range: '{}'+'{}'", src.substr(0, len), src.substr(len, 4)));
839 if (src.starts_with(
"-")) {
840 value = std::numeric_limits<T>::lowest();
842 value = std::numeric_limits<T>::max();
844 }
else if (result.ec != std::errc{}) {
845 if (log_errors) LogError(fmt::format(
"Cannot parse integer: '{}'+'{}'", src.substr(0, len), src.substr(len, 4)));
861 [[nodiscard]] std::pair<size_type, T>
PeekIntegerBase(
int base,
bool clamp =
false)
const
863 return ParseIntegerBase<T>(this->src.substr(this->position), base, clamp,
false);
861 [[nodiscard]] std::pair<size_type, T>
PeekIntegerBase(
int base,
bool clamp =
false)
const {
…}
876 auto [len, value] = this->PeekIntegerBase<T>(base, clamp);
877 if (len == 0)
return std::nullopt;
893 auto [len, value] = ParseIntegerBase<T>(this->src.substr(this->position), base, clamp,
true);
895 return len > 0 ? value : def;
916template <
class T = u
int32_t>
917static inline std::optional<T>
ParseInteger(std::string_view arg,
int base = 10,
bool clamp =
false)
921 auto result = consumer.TryReadIntegerBase<T>(base, clamp);
923 if (consumer.AnyBytesLeft())
return std::nullopt;
917static inline std::optional<T>
ParseInteger(std::string_view arg,
int base = 10,
bool clamp =
false) {
…}
Parse data from a string / buffer.
bool ReadCharIf(char c)
Check whether the next 8-bit char matches 'c', and skip it.
std::string_view Peek(size_type len) const
Peek the next 'len' bytes.
std::optional< T > TryReadIntegerBase(int base, bool clamp=false)
Try to read and parse an integer in number 'base', and then advance the reader.
void SkipUint64LE()
Skip binary uint64, and advance reader.
bool PeekUtf8If(char32_t c) const
Check whether the next UTF-8 char matches 'c'.
std::string_view GetOrigData() const noexcept
Get the original data, as passed to the constructor.
void SkipCharIfIn(std::string_view chars)
If the next 8-bit char is in 'chars', skip it.
std::optional< uint32_t > TryReadUint32LE()
Try to read binary uint32, and then advance reader.
char32_t ReadUtf8(char32_t def='?')
Read UTF-8 character, and advance reader.
void SkipCharIf(char c)
If the next data matches the 8-bit char 'c', then skip it.
std::string_view ReadUntilChar(char c, SeparatorUsage sep)
Read data until the first occurrence of 8-bit char 'c', and advance reader.
size_type FindChar(char c) const
Find first occurence of 8-bit char 'c'.
void SkipUntilChar(char c, SeparatorUsage sep)
Skip data until the first occurrence of 8-bit char 'c'.
SeparatorUsage
Treatment of separator characters.
@ READ_ALL_SEPARATORS
Read all consecutive separators, and include them all in the result.
@ READ_ONE_SEPARATOR
Read one separator, and include it in the result.
@ SKIP_ALL_SEPARATORS
Read and discard all consecutive separators, do not include any in the result.
@ SKIP_ONE_SEPARATOR
Read and discard one separator, do not include it in the result.
@ KEEP_SEPARATOR
Keep the separator in the data as next value to be read.
int16_t ReadSint16LE(int16_t def=0)
Read binary int16 using little endian, and advance reader.
void SkipUtf8()
Skip UTF-8 character, and advance reader.
std::optional< char > PeekCharIfIn(std::string_view chars) const
Check whether the next 8-bit char is in 'chars'.
StringConsumer(std::string_view src)
Construct parser with data from string.
char ReadChar(char def='?')
Read 8-bit character, and advance reader.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
uint8_t ReadUint8(uint8_t def=0)
Read binary uint8, and advance reader.
bool PeekCharIf(char c) const
Check whether the next 8-bit char matches 'c'.
std::optional< uint32_t > PeekUint32LE() const
Peek binary uint32 using little endian.
std::optional< uint16_t > TryReadUint16LE()
Try to read binary uint16, and then advance reader.
size_type GetBytesLeft() const noexcept
Get number of bytes left to read.
std::string_view ReadUntilCharNotIn(std::string_view chars)
Read 8-bit chars, while they are in 'chars', until they are not; and advance reader.
std::string_view PeekUntil(std::string_view str, SeparatorUsage sep) const
Peek data until the first occurrence of 'str'.
std::optional< char > PeekCharIfNotIn(std::string_view chars) const
Check whether the next 8-bit char is not in 'chars'.
void SkipUntilCharIn(std::string_view chars)
Skip 8-bit chars, while they are not in 'chars', until they are.
int8_t ReadSint8(int8_t def=0)
Read binary int8, and advance reader.
void SkipUint16LE()
Skip binary uint16, and advance reader.
std::optional< int8_t > PeekSint8() const
Peek binary int8.
uint16_t ReadUint16LE(uint16_t def=0)
Read binary uint16 using little endian, and advance reader.
void SkipChar()
Skip 8-bit character, and advance reader.
static const std::string_view WHITESPACE_OR_NEWLINE
ASCII whitespace characters, including new-line.
void SkipSint8()
Skip binary int8.
void SkipUint32LE()
Skip binary uint32, and advance reader.
std::string_view ReadUntil(std::string_view str, SeparatorUsage sep)
Read data until the first occurrence of 'str', and advance reader.
void SkipUntil(std::string_view str, SeparatorUsage sep)
Skip data until the first occurrence of 'str'.
int64_t ReadSint64LE(int64_t def=0)
Read binary int64 using little endian, and advance reader.
static const std::string_view WHITESPACE_NO_NEWLINE
ASCII whitespace characters, excluding new-line.
std::optional< char > PeekChar() const
Peek 8-bit character.
std::optional< int16_t > PeekSint16LE() const
Peek binary int16 using little endian.
std::pair< size_type, T > PeekIntegerBase(int base, bool clamp=false) const
Peek and parse an integer in number 'base'.
std::optional< char > ReadCharIfIn(std::string_view chars)
Read next 8-bit char, check whether it is in 'chars', and advance reader.
std::optional< char > TryReadChar()
Try to read a 8-bit character, and then advance reader.
bool ReadIf(std::string_view str)
Check whether the next data matches 'str', and skip it.
std::string_view PeekUntilUtf8(char32_t c, SeparatorUsage sep) const
Peek data until the first occurrence of UTF-8 char 'c'.
std::optional< uint64_t > PeekUint64LE() const
Peek binary uint64 using little endian.
void SkipUntilCharNotIn(std::string_view chars)
Skip 8-bit chars, while they are in 'chars', until they are not.
bool AnyBytesRead() const noexcept
Check wheter any bytes were already read.
std::optional< int32_t > TryReadSint32LE()
Try to read binary int32, and then advance reader.
void SkipUint8()
Skip binary uint8.
void SkipAll()
Discard all remaining data.
StringConsumer(const std::string &src)
Construct parser with data from string.
std::string_view ReadUntilCharIn(std::string_view chars)
Read 8-bit chars, while they are not in 'chars', until they are; and advance reader.
uint32_t ReadUint32LE(uint32_t def=0)
Read binary uint32 using little endian, and advance reader.
std::optional< int32_t > PeekSint32LE() const
Peek binary int32 using little endian.
std::optional< char32_t > TryReadUtf8()
Try to read a UTF-8 character, and then advance reader.
int32_t ReadSint32LE(int32_t def=0)
Read binary int32 using little endian, and advance reader.
void SkipCharIfNotIn(std::string_view chars)
If the next 8-bit char is not in 'chars', skip it.
size_type FindCharNotIn(std::string_view chars) const
Find first occurence of any 8-bit char not in 'chars'.
void SkipIf(std::string_view str)
If the next data matches 'str', then skip it.
std::optional< uint16_t > PeekUint16LE() const
Peek binary uint16 using little endian.
std::optional< uint64_t > TryReadUint64LE()
Try to read binary uint64, and then advance reader.
std::optional< uint8_t > TryReadUint8()
Try to read binary uint8, and then advance reader.
T ReadIntegerBase(int base, T def=0, bool clamp=false)
Read and parse an integer in number 'base', and advance the reader.
std::string_view Read(size_type len)
Read the next 'len' bytes, and advance reader.
bool ReadUtf8If(char32_t c)
Check whether the next UTF-8 char matches 'c', and skip it.
void SkipIntegerBase(int base)
Skip an integer in number 'base'.
void SkipSint64LE()
Skip binary int64, and advance reader.
std::string_view GetReadData() const noexcept
Get already read data.
std::optional< int64_t > PeekSint64LE() const
Peek binary int64 using little endian.
static constexpr size_type npos
Special value for "end of data".
std::string_view GetLeftData() const noexcept
Get data left to read.
size_type GetBytesRead() const noexcept
Get number of already read bytes.
void Skip(size_type len)
Discard some bytes.
size_type Find(std::string_view str) const
Find first occurence of 'str'.
bool PeekIf(std::string_view str) const
Check whether the next data matches 'str'.
std::optional< uint8_t > PeekUint8() const
Peek binary uint8.
std::string_view PeekUntilChar(char c, SeparatorUsage sep) const
Peek data until the first occurrence of 8-bit char 'c'.
void SkipUtf8If(char32_t c)
If the next data matches the UTF-8 char 'c', then skip it.
uint64_t ReadUint64LE(uint64_t def=0)
Read binary uint64 using little endian, and advance reader.
std::optional< int64_t > TryReadSint64LE()
Try to read binary int64, and then advance reader.
void SkipSint32LE()
Skip binary int32, and advance reader.
std::string_view PeekUntilCharIn(std::string_view chars) const
Peek 8-bit chars, while they are not in 'chars', until they are.
std::optional< char > ReadCharIfNotIn(std::string_view chars)
Read next 8-bit char, check whether it is not in 'chars', and advance reader.
void SkipUntilUtf8(char32_t c, SeparatorUsage sep)
Skip data until the first occurrence of UTF-8 char 'c'.
std::optional< int8_t > TryReadSint8()
Try to read binary int8, and then advance reader.
void SkipSint16LE()
Skip binary int16, and advance reader.
StringConsumer(std::span< const char > src)
Construct parser with data from span.
std::string_view ReadUntilUtf8(char32_t c, SeparatorUsage sep)
Read data until the first occurrence of UTF-8 char 'c', and advance reader.
std::string_view PeekUntilCharNotIn(std::string_view chars) const
Peek 8-bit chars, while they are in 'chars', until they are not.
std::pair< size_type, char32_t > PeekUtf8() const
Peek UTF-8 character.
size_type FindCharIn(std::string_view chars) const
Find first occurence of any 8-bit char in 'chars'.
std::optional< int16_t > TryReadSint16LE()
Try to read binary int16, and then advance reader.
size_type FindUtf8(char32_t c) const
Find first occurence of UTF-8 char 'c'.
static std::optional< T > ParseInteger(std::string_view arg, int base=10, bool clamp=false)
Change a string into its number representation.