summaryrefslogtreecommitdiffstats
path: root/src/third-party/scnlib/include/scn/reader/common.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/third-party/scnlib/include/scn/reader/common.h')
-rw-r--r--src/third-party/scnlib/include/scn/reader/common.h1663
1 files changed, 1663 insertions, 0 deletions
diff --git a/src/third-party/scnlib/include/scn/reader/common.h b/src/third-party/scnlib/include/scn/reader/common.h
new file mode 100644
index 0000000..0f2b83b
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/reader/common.h
@@ -0,0 +1,1663 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_READER_COMMON_H
+#define SCN_READER_COMMON_H
+
+#include "../detail/error.h"
+#include "../detail/locale.h"
+#include "../detail/range.h"
+#include "../unicode/unicode.h"
+#include "../util/algorithm.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ // read_code_unit
+
+ namespace detail {
+ template <typename WrappedRange>
+ expected<typename WrappedRange::char_type>
+ read_code_unit_impl(WrappedRange& r, bool advance, std::true_type)
+ {
+ SCN_CLANG_PUSH
+ // clang 10 behaves weirdly
+ SCN_CLANG_IGNORE("-Wzero-as-null-pointer-constant")
+ SCN_EXPECT(r.begin() < r.end());
+ SCN_CLANG_POP
+ auto ch = *r.begin();
+ if (advance) {
+ r.advance();
+ }
+ return {ch};
+ }
+ template <typename WrappedRange>
+ expected<typename WrappedRange::char_type>
+ read_code_unit_impl(WrappedRange& r, bool advance, std::false_type)
+ {
+ SCN_EXPECT(r.begin() != r.end());
+ auto ch = *r.begin();
+ if (advance && ch) {
+ r.advance();
+ }
+ return ch;
+ }
+ } // namespace detail
+
+ /**
+ * Reads a single character (= code unit) from the range.
+ * Dereferences the begin iterator, wrapping it in an `expected` if
+ * necessary.
+ *
+ * Encoding-agnostic, doesn't care about code points, and may leave behind
+ * partial ones.
+ *
+ * \param r Range to read from
+ * \param advance If `true`, and the read was successful, the range is
+ * advanced by a single character, as if by calling `r.advance()`.
+ *
+ * \return The next character in the range, obtained as if by dereferencing
+ * the begin iterator `*r.begin()`.
+ * If `r.begin() == r.end()`, returns EOF.
+ * If `r` is direct, returns `*r.begin()` wrapped in an `expected`.
+ * If `r` is not direct, returns `*r.begin()` as-is, with any errors that
+ * may have been caused by the read.
+ */
+ template <typename WrappedRange>
+ expected<typename WrappedRange::char_type> read_code_unit(
+ WrappedRange& r,
+ bool advance = true)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return detail::read_code_unit_impl(
+ r, advance,
+ std::integral_constant<bool, WrappedRange::is_direct>{});
+ }
+
+ // putback_n
+
+ /// @{
+
+ /**
+ * Puts back `n` characters (= code units) into `r` as if by repeatedly
+ * calling `r.advance(-1)`.
+ *
+ * Encoding-agnostic, may leave behind partial code points.
+ *
+ * \param r Range to roll back
+ * \param n Characters to put back, must be less than or equal to the number
+ * of characters already read from `r`.
+ *
+ * \return If `r` is contiguous, will always return `error::good`.
+ * Otherwise, may return `error::unrecoverable_source_error`, if the putback
+ * fails.
+ */
+ template <
+ typename WrappedRange,
+ typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
+ error putback_n(WrappedRange& r, ranges::range_difference_t<WrappedRange> n)
+ {
+ SCN_EXPECT(n <= ranges::distance(r.begin_underlying(), r.begin()));
+ r.advance(-n);
+ return {};
+ }
+ template <
+ typename WrappedRange,
+ typename std::enable_if<!WrappedRange::is_contiguous>::type* = nullptr>
+ error putback_n(WrappedRange& r, ranges::range_difference_t<WrappedRange> n)
+ {
+ for (ranges::range_difference_t<WrappedRange> i = 0; i < n; ++i) {
+ r.advance(-1);
+ if (r.begin() == r.end()) {
+ return {error::unrecoverable_source_error, "Putback failed"};
+ }
+ }
+ return {};
+ }
+
+ /// @}
+
+ // read_code_point
+
+ /**
+ * Type returned by `read_code_point`
+ * \tparam CharT Character type of the range
+ */
+ template <typename CharT>
+ struct read_code_point_result {
+ /// Code units, may point to `writebuf` given to `read_code_point`
+ span<const CharT> chars;
+ /// Parsed code point
+ code_point cp;
+ };
+
+ namespace detail {
+ // contiguous && direct
+ template <typename CharT, typename WrappedRange>
+ expected<read_code_point_result<CharT>> read_code_point_impl(
+ WrappedRange& r,
+ span<CharT> writebuf,
+ std::true_type)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+
+ auto sbuf = r.get_buffer_and_advance(4 / sizeof(CharT));
+ if (sbuf.size() == 0) {
+ auto ret = read_code_unit(r, true);
+ if (!ret) {
+ return ret.error();
+ }
+ sbuf = writebuf.first(1);
+ writebuf[0] = ret.value();
+ }
+ int len = ::scn::get_sequence_length(sbuf[0]);
+ if (SCN_UNLIKELY(len == 0)) {
+ return error(error::invalid_encoding, "Invalid code point");
+ }
+ if (sbuf.ssize() > len) {
+ auto e = putback_n(r, sbuf.ssize() - len);
+ if (!e) {
+ return e;
+ }
+ sbuf = sbuf.first(static_cast<size_t>(len));
+ }
+ if (len == 1) {
+ // Single-char code point
+ return read_code_point_result<CharT>{sbuf.first(1),
+ make_code_point(sbuf[0])};
+ }
+ while (sbuf.ssize() < len) {
+ auto ret = read_code_unit(r, true);
+ if (!ret) {
+ auto e = putback_n(r, sbuf.ssize());
+ if (!e) {
+ return e;
+ }
+ if (ret.error().code() == error::end_of_range) {
+ return error(error::invalid_encoding,
+ "Invalid code point");
+ }
+ return ret.error();
+ }
+ sbuf = make_span(writebuf.begin(), sbuf.size() + 1);
+ writebuf[sbuf.size() - 1] = ret.value();
+ }
+
+ code_point cp{};
+ auto ret = parse_code_point(sbuf.begin(), sbuf.end(), cp);
+ if (!ret) {
+ return ret.error();
+ }
+ return read_code_point_result<CharT>{sbuf, cp};
+ }
+
+ template <typename CharT, typename WrappedRange>
+ expected<read_code_point_result<CharT>> read_code_point_impl(
+ WrappedRange& r,
+ span<CharT> writebuf,
+ std::false_type)
+ {
+ auto first = read_code_unit(r, false);
+ if (!first) {
+ return first.error();
+ }
+
+ auto len =
+ static_cast<size_t>(::scn::get_sequence_length(first.value()));
+ if (SCN_UNLIKELY(len == 0)) {
+ return error(error::invalid_encoding, "Invalid code point");
+ }
+ r.advance();
+
+ writebuf[0] = first.value();
+ if (len == 1) {
+ // Single-char code point
+ return read_code_point_result<CharT>{
+ make_span(writebuf.data(), 1),
+ make_code_point(first.value())};
+ }
+
+ size_t index = 1;
+
+ auto parse = [&]() -> expected<read_code_point_result<CharT>> {
+ code_point cp{};
+ auto ret = parse_code_point(writebuf.data(),
+ writebuf.data() + len, cp);
+ if (!ret) {
+ auto pb = putback_n(r, static_cast<std::ptrdiff_t>(len));
+ if (!pb) {
+ return pb;
+ }
+ return ret.error();
+ }
+ auto s = make_span(writebuf.data(), len);
+ return read_code_point_result<CharT>{s, cp};
+ };
+ auto advance = [&]() -> error {
+ auto ret = read_code_unit(r, false);
+ if (!ret) {
+ auto pb = putback_n(r, static_cast<std::ptrdiff_t>(index));
+ if (!pb) {
+ return pb;
+ }
+ return ret.error();
+ }
+ writebuf[index] = ret.value();
+ ++index;
+ r.advance();
+ return {};
+ };
+
+ while (index < 4) {
+ auto e = advance();
+ if (!e) {
+ return e;
+ }
+ if (index == len) {
+ return parse();
+ }
+ }
+ SCN_ENSURE(false);
+ SCN_UNREACHABLE;
+ }
+ } // namespace detail
+
+ /**
+ * Read a single Unicode code point from `r` as if by repeatedly calling
+ * `read_code_unit()`.
+ *
+ * Advances the range past the read code point. On error, rolls back the
+ * range into the state it was before calling this function, as if by
+ * calling `putback_n()`.
+ *
+ * \param r Range to read from
+ * \param writebuf Buffer to use for reading into, if necessary. `BufValueT`
+ * can be any trivial type. Must be at least 4 bytes long. May be written
+ * over.
+ *
+ * \return An instance of `read_code_point_result`, wrapped in an
+ * `expected`. `chars` contains the code units read from `r`, which may
+ * point to `writebuf`. `cp` contains the code point parsed.
+ * If `r.begin() == r.end()`, returns EOF.
+ * If `read_code_unit()` or `putback_n()` fails, returns any errors returned
+ * by it.
+ * If the code point was not encoded correctly, returns
+ * `error::invalid_encoding`.
+ */
+ template <typename WrappedRange, typename BufValueT>
+ expected<read_code_point_result<typename WrappedRange::char_type>>
+ read_code_point(WrappedRange& r, span<BufValueT> writebuf)
+ {
+ SCN_EXPECT(writebuf.size() * sizeof(BufValueT) >= 4);
+ using char_type = typename WrappedRange::char_type;
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wcast-align") // taken care of by the caller
+ return detail::read_code_point_impl<char_type>(
+ r,
+ make_span(reinterpret_cast<char_type*>(writebuf.data()),
+ writebuf.size() * sizeof(BufValueT) / sizeof(char_type)),
+ std::integral_constant<bool,
+ WrappedRange::provides_buffer_access>{});
+ SCN_GCC_POP
+ }
+
+ // read_zero_copy
+
+ /// @{
+
+ /**
+ * Reads up to `n` characters (= code units) from `r`, as if by repeatedly
+ * incrementing `r.begin()`, and returns a `span` pointing into `r`.
+ *
+ * Let `count` be `min(r.size(), n)`.
+ * Reads, and advances `r` by `count` characters.
+ * `r.begin()` is in no point dereferenced.
+ * If `r.size()` is not defined, the range is not contiguous, and an empty
+ * span is returned.
+ *
+ * \return A `span` pointing to `r`, starting from `r.begin()` and with a
+ * size of `count`.
+ * If `r.begin() == r.end()`, returns EOF.
+ * If the range does not satisfy `contiguous_range`, returns an empty
+ * `span`.
+ */
+ template <typename WrappedRange,
+ typename std::enable_if<
+ WrappedRange::provides_buffer_access>::type* = nullptr>
+ expected<span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>>
+ read_zero_copy(WrappedRange& r, ranges::range_difference_t<WrappedRange> n)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return r.get_buffer_and_advance(static_cast<size_t>(n));
+ }
+ template <typename WrappedRange,
+ typename std::enable_if<
+ !WrappedRange::provides_buffer_access>::type* = nullptr>
+ expected<span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>>
+ read_zero_copy(WrappedRange& r, ranges::range_difference_t<WrappedRange>)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>{};
+ }
+ /// @}
+
+ // read_all_zero_copy
+
+ /// @{
+ /**
+ * Reads every character from `r`, as if by repeatedly incrementing
+ * `r.begin()`, and returns a `span` pointing into `r`.
+ *
+ * If there's no error, `r` is advanced to the end.
+ * `r.begin()` is in no point dereferenced.
+ * If `r.size()` is not defined, the range is not contiguous, and an empty
+ * span is returned.
+ *
+ * \return A `span` pointing to `r`, starting at `r.begin()` and ending at
+ * `r.end()`.
+ * If `r.begin() == r.end()`, returns EOF.
+ * If the range does not satisfy `contiguous_range`, returns an empty
+ * `span`.
+ */
+ template <
+ typename WrappedRange,
+ typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
+ expected<span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>>
+ read_all_zero_copy(WrappedRange& r)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ auto s = make_span(r.data(), static_cast<size_t>(r.size()));
+ r.advance(r.size());
+ return s;
+ }
+ template <
+ typename WrappedRange,
+ typename std::enable_if<!WrappedRange::is_contiguous>::type* = nullptr>
+ expected<span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>>
+ read_all_zero_copy(WrappedRange& r)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>{};
+ }
+ /// @}
+
+ // read_into
+
+ namespace detail {
+ template <typename WrappedRange, typename OutputIterator>
+ error read_into_impl(WrappedRange& r,
+ OutputIterator& it,
+ ranges::range_difference_t<WrappedRange> n)
+ {
+ for (; n != 0; --n) {
+ auto ret = read_code_unit(r, false);
+ if (!ret) {
+ return ret.error();
+ }
+ *it = ret.value();
+ r.advance();
+ }
+ return {};
+ }
+ } // namespace detail
+
+ /// @{
+
+ /**
+ * Reads up to `n` characters (= code units) from `r`, as if by repeatedly
+ * calling `read_code_unit()`, and writing the characters into `it`.
+ *
+ * If reading fails at any point, the error is returned.
+ * `r` is advanced by as many characters that were successfully read.
+ *
+ * \param r Range to read
+ * \param it Iterator to write into, e.g. `std::back_insert_iterator`. Must
+ * satisfy `output_iterator`, and be incrementable by `n` times.
+ * \param n Characters to read from `r`
+ *
+ * \return `error::good` if `n` characters were read.
+ * If `r.begin() == r.end()` at any point before `n` characters has been
+ * read, returns EOF.
+ * Any error returned by `read_code_unit()` if one
+ * occurred.
+ */
+ template <typename WrappedRange,
+ typename OutputIterator,
+ typename std::enable_if<
+ WrappedRange::provides_buffer_access>::type* = nullptr>
+ error read_into(WrappedRange& r,
+ OutputIterator& it,
+ ranges::range_difference_t<WrappedRange> n)
+ {
+ while (n != 0) {
+ if (r.begin() == r.end()) {
+ return {error::end_of_range, "EOF"};
+ }
+ auto s = read_zero_copy(r, n);
+ if (!s) {
+ return s.error();
+ }
+ if (s.value().size() == 0) {
+ break;
+ }
+ it = std::copy(s.value().begin(), s.value().end(), it);
+ n -= s.value().ssize();
+ }
+ if (n != 0) {
+ return detail::read_into_impl(r, it, n);
+ }
+ return {};
+ }
+ template <typename WrappedRange,
+ typename OutputIterator,
+ typename std::enable_if<
+ !WrappedRange::provides_buffer_access>::type* = nullptr>
+ error read_into(WrappedRange& r,
+ OutputIterator& it,
+ ranges::range_difference_t<WrappedRange> n)
+ {
+ if (r.begin() == r.end()) {
+ return {error::end_of_range, "EOF"};
+ }
+ return detail::read_into_impl(r, it, n);
+ }
+ /// @}
+
+ namespace detail {
+ template <typename WrappedRange, typename Predicate>
+ expected<span<const typename WrappedRange::char_type>>
+ read_until_pred_contiguous(WrappedRange& r,
+ Predicate&& pred,
+ bool pred_result_to_stop,
+ bool keep_final)
+ {
+ using span_type = span<const typename WrappedRange::char_type>;
+
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+
+ if (!pred.is_multibyte()) {
+ for (auto it = r.begin(); it != r.end(); ++it) {
+ if (pred(make_span(&*it, 1)) == pred_result_to_stop) {
+ auto begin = r.data();
+ auto end = keep_final ? it + 1 : it;
+ r.advance_to(end);
+ return span_type{
+ begin, to_address_safe(end, r.begin(), r.end())};
+ }
+ }
+ }
+ else {
+ for (auto it = r.begin(); it != r.end();) {
+ auto len = ::scn::get_sequence_length(*it);
+ if (len == 0 || ranges::distance(it, r.end()) < len) {
+ return error{error::invalid_encoding,
+ "Invalid code point"};
+ }
+ auto span =
+ make_span(to_address_safe(it, r.begin(), r.end()),
+ static_cast<size_t>(len));
+ code_point cp{};
+ auto i = parse_code_point(span.begin(), span.end(), cp);
+ if (!i) {
+ return i.error();
+ }
+ if (i.value() != span.end()) {
+ return error{error::invalid_encoding,
+ "Invalid code point"};
+ }
+ if (pred(span) == pred_result_to_stop) {
+ auto begin = r.data();
+ auto end = keep_final ? it + len : it;
+ r.advance_to(end);
+ return span_type{
+ begin, to_address_safe(end, r.begin(), r.end())};
+ }
+ it += len;
+ }
+ }
+ auto begin = r.data();
+ auto end = r.data() + r.size();
+ r.advance_to(r.end());
+ return span_type{begin, end};
+ }
+ } // namespace detail
+
+ // read_until_space_zero_copy
+
+ namespace detail {
+ template <typename WrappedRange, typename Predicate>
+ expected<span<const typename WrappedRange::char_type>>
+ read_until_space_zero_copy_impl(WrappedRange& r,
+ Predicate&& is_space,
+ bool keep_final_space,
+ std::true_type)
+ {
+ return detail::read_until_pred_contiguous(r, SCN_FWD(is_space),
+ true, keep_final_space);
+ }
+ template <typename WrappedRange, typename Predicate>
+ expected<span<const typename WrappedRange::char_type>>
+ read_until_space_zero_copy_impl(WrappedRange& r,
+ Predicate&&,
+ bool,
+ std::false_type)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return span<const typename WrappedRange::char_type>{};
+ }
+ } // namespace detail
+
+ /**
+ * Reads code points from `r`, until a space, as determined by `is_space`,
+ * is found, and returns a `span` pointing to `r`.
+ *
+ * If no error occurs `r` is advanced past the returned span.
+ * On error, `r` is not advanced.
+ *
+ * \param r Range to read from
+ *
+ * \param is_space Predicate taking a span of code units encompassing a code
+ * point, and returning a `bool`, where `true` means that the character is a
+ * space. Additionally, it must have a member function
+ * `is_space.is_multibyte()`, returning a `bool`, where `true` means that a
+ * space character can encompass multiple code units.
+ *
+ * \param keep_final_space If `true`, the space code point found is included
+ * in the returned span, and it is advanced past in `r`. If `false`, it is
+ * not included, and `r.begin()` will point to the space.
+ *
+ * \return Span of code units, pointing to `r`, starting at `r.begin()`, and
+ * ending at the space character, the precise location determined by the
+ * `keep_final_space` parameter.
+ * If `r.begin() == r.end()`, returns EOF.
+ * `r` reaching its end before a space character is found is not considered
+ * an error.
+ * If `r` contains invalid encoding, returns `error::invalid_encoding`.
+ * If the range is not contiguous, returns an empty `span`.
+ */
+ template <typename WrappedRange, typename Predicate>
+ expected<span<const typename WrappedRange::char_type>>
+ read_until_space_zero_copy(WrappedRange& r,
+ Predicate&& is_space,
+ bool keep_final_space)
+ {
+ return detail::read_until_space_zero_copy_impl(
+ r, SCN_FWD(is_space), keep_final_space,
+ std::integral_constant<bool, WrappedRange::is_contiguous>{});
+ }
+
+ // read_until_space
+
+ namespace detail {
+ template <typename WrappedRange,
+ typename Predicate,
+ typename OutputIt,
+ typename OutputItCmp>
+ error read_until_pred_buffer(WrappedRange& r,
+ Predicate&& pred,
+ bool pred_result_to_stop,
+ OutputIt& out,
+ OutputItCmp out_cmp,
+ bool keep_final,
+ bool& done,
+ std::true_type)
+ {
+ if (!pred.is_multibyte()) {
+ while (r.begin() != r.end() && !done) {
+ auto s = r.get_buffer_and_advance();
+ for (auto it = s.begin(); it != s.end() && out_cmp(out);
+ ++it) {
+ if (pred(make_span(&*it, 1)) == pred_result_to_stop) {
+ if (keep_final) {
+ *out = *it;
+ ++out;
+ }
+ auto e =
+ putback_n(r, ranges::distance(it, s.end()));
+ if (!e) {
+ return e;
+ }
+ done = true;
+ break;
+ }
+ *out = *it;
+ ++out;
+ }
+ if (!done && out_cmp(out)) {
+ auto ret = read_code_unit(r, false);
+ if (!ret) {
+ if (ret.error() == error::end_of_range) {
+ return {};
+ }
+ return ret.error();
+ }
+ if (pred(make_span(&ret.value(), 1)) ==
+ pred_result_to_stop) {
+ if (keep_final) {
+ r.advance();
+ *out = ret.value();
+ ++out;
+ }
+ done = true;
+ break;
+ }
+ r.advance();
+ *out = ret.value();
+ ++out;
+ }
+ }
+ }
+ else {
+ while (r.begin() != r.end() && !done) {
+ auto s = r.get_buffer_and_advance();
+ for (auto it = s.begin(); it != s.end() && out_cmp(out);) {
+ auto len = ::scn::get_sequence_length(*it);
+ if (len == 0) {
+ return error{error::invalid_encoding,
+ "Invalid code point"};
+ }
+ if (ranges::distance(it, s.end()) < len) {
+ auto e = putback_n(r, len);
+ if (!e) {
+ return e;
+ }
+ break;
+ }
+ auto cpspan = make_span(it, static_cast<size_t>(len));
+ code_point cp{};
+ auto i =
+ parse_code_point(cpspan.begin(), cpspan.end(), cp);
+ if (!i) {
+ return i.error();
+ }
+ if (i.value() != cpspan.end()) {
+ return error{error::invalid_encoding,
+ "Invalid code point"};
+ }
+ if (pred(cpspan) == pred_result_to_stop) {
+ if (keep_final) {
+ out = std::copy(cpspan.begin(), cpspan.end(),
+ out);
+ }
+ done = true;
+ break;
+ }
+ out = std::copy(cpspan.begin(), cpspan.end(), out);
+ }
+
+ if (!done && out_cmp(out)) {
+ alignas(typename WrappedRange::char_type) unsigned char
+ buf[4] = {0};
+ auto cpret = read_code_point(r, make_span(buf, 4));
+ if (!cpret) {
+ if (cpret.error() == error::end_of_range) {
+ return {};
+ }
+ return cpret.error();
+ }
+ if (pred(cpret.value().chars) == pred_result_to_stop) {
+ if (keep_final) {
+ out = std::copy(cpret.value().chars.begin(),
+ cpret.value().chars.end(), out);
+ }
+ else {
+ return putback_n(r,
+ cpret.value().chars.ssize());
+ }
+ done = true;
+ break;
+ }
+ out = std::copy(cpret.value().chars.begin(),
+ cpret.value().chars.end(), out);
+ }
+ }
+ }
+ return {};
+ }
+ template <typename WrappedRange,
+ typename Predicate,
+ typename OutputIt,
+ typename OutputItCmp>
+ error read_until_pred_buffer(WrappedRange&,
+ Predicate&&,
+ bool,
+ OutputIt&,
+ OutputItCmp,
+ bool,
+ bool& done,
+ std::false_type)
+ {
+ done = false;
+ return {};
+ }
+
+ template <typename WrappedRange,
+ typename Predicate,
+ typename OutputIt,
+ typename OutputItCmp>
+ error read_until_pred_non_contiguous(WrappedRange& r,
+ Predicate&& pred,
+ bool pred_result_to_stop,
+ OutputIt& out,
+ OutputItCmp out_cmp,
+ bool keep_final)
+ {
+ if (r.begin() == r.end()) {
+ return {error::end_of_range, "EOF"};
+ }
+
+ {
+ bool done = false;
+ auto e = read_until_pred_buffer(
+ r, pred, pred_result_to_stop, out, out_cmp, keep_final,
+ done,
+ std::integral_constant<
+ bool, WrappedRange::provides_buffer_access>{});
+ if (!e) {
+ return e;
+ }
+ if (done) {
+ return {};
+ }
+ }
+
+ if (!pred.is_multibyte()) {
+ while (r.begin() != r.end() && out_cmp(out)) {
+ auto cu = read_code_unit(r, false);
+ if (!cu) {
+ return cu.error();
+ }
+ if (pred(make_span(&cu.value(), 1)) ==
+ pred_result_to_stop) {
+ if (keep_final) {
+ r.advance();
+ *out = cu.value();
+ ++out;
+ }
+ return {};
+ }
+ r.advance();
+ *out = cu.value();
+ ++out;
+ }
+ }
+ else {
+ unsigned char buf[4] = {0};
+ while (r.begin() != r.end() && out_cmp(out)) {
+ auto cp = read_code_point(r, make_span(buf, 4));
+ if (!cp) {
+ return cp.error();
+ }
+ if (pred(cp.value().chars) == pred_result_to_stop) {
+ if (keep_final) {
+ out = std::copy(cp.value().chars.begin(),
+ cp.value().chars.end(), out);
+ return {};
+ }
+ else {
+ return putback_n(r, cp.value().chars.ssize());
+ }
+ }
+ out = std::copy(cp.value().chars.begin(),
+ cp.value().chars.end(), out);
+ }
+ }
+ return {};
+ }
+ } // namespace detail
+
+ /// @{
+
+ /**
+ * Reads code points from `r`, until a space, as determined by `is_space`,
+ * is found, and writes them into `out`, a single code unit at a time.
+ *
+ * If no error occurs, `r` is advanced past the last character written into
+ * `out`.
+ *
+ * On error, `r` is advanced an indeterminate amount, as if by calling
+ * `r.advance(n)`, where `n` is a non-negative integer.
+ * It is, however, not advanced past any space characters.
+ *
+ * \param r Range to read from
+ *
+ * \param out Iterator to write read characters into. Must satisfy
+ * `output_iterator`.
+ *
+ * \param is_space Predicate taking a span of code units encompassing a code
+ * point, and returning a `bool`, where `true` means that the character is a
+ * space. Additionally, it must have a member function
+ * `is_space.is_multibyte()`, returning a `bool`, where `true` means that a
+ * space character can encompass multiple code units.
+ *
+ * \param keep_final_space If `true`, the space code point found is written
+ * into `out`, and it is advanced past in `r`. If `false`, it is not
+ * included, and `r.begin()` will point to the space.
+ *
+ * \return `error::good` on success.
+ * If `r.begin() == r.end()`, returns EOF.
+ * `r` reaching its end before a space character is found is not considered
+ * an error.
+ * If `r` contains invalid encoding, returns `error::invalid_encoding`.
+ */
+ template <
+ typename WrappedRange,
+ typename OutputIterator,
+ typename Predicate,
+ typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
+ error read_until_space(WrappedRange& r,
+ OutputIterator& out,
+ Predicate&& is_space,
+ bool keep_final_space)
+ {
+ auto s =
+ read_until_space_zero_copy(r, SCN_FWD(is_space), keep_final_space);
+ if (!s) {
+ return s.error();
+ }
+ out = std::copy(s.value().begin(), s.value().end(), out);
+ return {};
+ }
+ template <
+ typename WrappedRange,
+ typename OutputIterator,
+ typename Predicate,
+ typename std::enable_if<!WrappedRange::is_contiguous>::type* = nullptr>
+ error read_until_space(WrappedRange& r,
+ OutputIterator& out,
+ Predicate&& is_space,
+ bool keep_final_space)
+ {
+ return detail::read_until_pred_non_contiguous(
+ r, SCN_FWD(is_space), true, out,
+ [](const OutputIterator&) { return true; }, keep_final_space);
+ }
+
+ /// @}
+
+ // read_until_space_ranged
+
+ /// @{
+
+ /**
+ * Otherwise equivalent to `read_until_space`, except will also stop reading
+ * if `out == end`.
+ *
+ * \see read_until_space
+ */
+ template <typename WrappedRange,
+ typename OutputIterator,
+ typename Sentinel,
+ typename Predicate>
+ error read_until_space_ranged(WrappedRange& r,
+ OutputIterator& out,
+ Sentinel end,
+ Predicate&& is_space,
+ bool keep_final_space)
+ {
+ return detail::read_until_pred_non_contiguous(
+ r, SCN_FWD(is_space), true, out,
+ [&end](const OutputIterator& it) { return it != end; },
+ keep_final_space);
+ }
+
+ /// @}
+
+ namespace detail {
+ /**
+ * Predicate to pass to read_until_space etc.
+ */
+ template <typename CharT>
+ struct is_space_predicate {
+ using char_type = CharT;
+ using locale_type = basic_locale_ref<char_type>;
+
+ /**
+ * \param l Locale to use, fetched from `ctx.locale()`
+ * \param localized If `true`, use `l.get_custom()`, otherwise use
+ * `l.get_static()`.
+ * \param width If `width != 0`, limit the number of code
+ * units to be read
+ */
+ SCN_CONSTEXPR14 is_space_predicate(const locale_type& l,
+ bool localized,
+ size_t width)
+ : m_locale{nullptr},
+ m_width{width},
+ m_fn{get_fn(localized, width != 0)}
+ {
+ if (localized) {
+ l.prepare_localized();
+ m_locale = l.get_localized_unsafe();
+ }
+ }
+
+ /**
+ * Returns `true` if `ch` is a code point according to the supplied
+ * locale, using either the static or custom locale, depending on
+ * the `localized` parameter given to the constructor.
+ *
+ * Returns also `true` if the maximum width, as determined by the
+ * `width` parameter given to the constructor, was reached.
+ */
+ bool operator()(span<const char_type> ch)
+ {
+ SCN_EXPECT(m_fn);
+ SCN_EXPECT(ch.size() >= 1);
+ return m_fn(m_locale, ch, m_i, m_width);
+ }
+
+ /**
+ * Returns `true`, if `*this` uses the custom locale for classifying
+ * space characters
+ */
+ constexpr bool is_localized() const
+ {
+ return m_locale != nullptr;
+ }
+ /**
+ * Returns `true` if a space character can encompass multiple code
+ * units
+ */
+ constexpr bool is_multibyte() const
+ {
+ return is_localized() && is_multichar_type(CharT{});
+ }
+
+ private:
+ using static_locale_type = typename locale_type::static_type;
+ using custom_locale_type = typename locale_type::custom_type;
+ const custom_locale_type* m_locale;
+ size_t m_width{0}, m_i{0};
+
+ constexpr static bool call(const custom_locale_type*,
+ span<const char_type> ch,
+ size_t&,
+ size_t)
+ {
+ return static_locale_type::is_space(ch);
+ }
+ static bool localized_call(const custom_locale_type* locale,
+ span<const char_type> ch,
+ size_t&,
+ size_t)
+ {
+ SCN_EXPECT(locale != nullptr);
+ return locale->is_space(ch);
+ }
+ SCN_CONSTEXPR14 static bool call_counting(const custom_locale_type*,
+ span<const char_type> ch,
+ size_t& i,
+ size_t max)
+ {
+ SCN_EXPECT(i <= max);
+ if (i == max || i + ch.size() > max) {
+ return true;
+ }
+ i += ch.size();
+ return static_locale_type::is_space(ch);
+ }
+ static bool localized_call_counting(
+ const custom_locale_type* locale,
+ span<const char_type> ch,
+ size_t& i,
+ size_t max)
+ {
+ SCN_EXPECT(locale != nullptr);
+ SCN_EXPECT(i <= max);
+ if (i == max || i + ch.size() > max) {
+ return true;
+ }
+ i += ch.size();
+ return locale->is_space(ch);
+ }
+
+ using fn_type = bool (*)(const custom_locale_type*,
+ span<const char_type>,
+ size_t&,
+ size_t);
+ fn_type m_fn{nullptr};
+
+ static SCN_CONSTEXPR14 fn_type get_fn(bool localized, bool counting)
+ {
+ if (localized) {
+ return counting ? localized_call_counting : localized_call;
+ }
+ return counting ? call_counting : call;
+ }
+ };
+
+ template <typename CharT>
+ is_space_predicate<CharT> make_is_space_predicate(
+ const basic_locale_ref<CharT>& locale,
+ bool localized,
+ size_t width = 0)
+ {
+ return {locale, localized, width};
+ }
+
+ template <typename CharT>
+ struct basic_skipws_iterator {
+ using value_type = void;
+ using reference = void;
+ using pointer = void;
+ using size_type = size_t;
+ using difference_type = std::ptrdiff_t;
+ using iterator_category = std::output_iterator_tag;
+
+ constexpr basic_skipws_iterator() = default;
+
+ basic_skipws_iterator& operator=(CharT)
+ {
+ return *this;
+ }
+ basic_skipws_iterator& operator*()
+ {
+ return *this;
+ }
+ basic_skipws_iterator& operator++()
+ {
+ return *this;
+ }
+ };
+ } // namespace detail
+
+ // skip_range_whitespace
+
+ /// @{
+
+ /**
+ * Reads code points from `ctx.range()`, as if by repeatedly calling
+ * `read_code_point()`, until a non-space character is found, or EOF is
+ * reached. That non-space character is then put back into the range.
+ *
+ * Whether a character is a space, is determined by `ctx.locale()` and the
+ * `localized` parameter.
+ *
+ * \param ctx Context to get the range and locale from.
+ *
+ * \param localized If `true`, `ctx.locale().get_custom()` is used.
+ * Otherwise, `ctx.locale().get_static()` is used.
+ * In practice, means whether locale-specific whitespace characters are
+ * accepted, or just those given by `std::isspace` with the `"C"` locale.
+ *
+ * \return `error::good` on success.
+ * If `ctx.range().begin() == ctx.range().end()`, returns EOF.
+ * If `ctx.range()` contains invalid encoding, returns
+ * `error::invalid_encoding`.
+ */
+ template <typename Context,
+ typename std::enable_if<
+ !Context::range_type::is_contiguous>::type* = nullptr>
+ error skip_range_whitespace(Context& ctx, bool localized) noexcept
+ {
+ auto is_space_pred =
+ detail::make_is_space_predicate(ctx.locale(), localized);
+ auto it = detail::basic_skipws_iterator<typename Context::char_type>{};
+ return detail::read_until_pred_non_contiguous(
+ ctx.range(), is_space_pred, false, it,
+ [](decltype(it)) { return true; }, false);
+ }
+ template <typename Context,
+ typename std::enable_if<
+ Context::range_type::is_contiguous>::type* = nullptr>
+ error skip_range_whitespace(Context& ctx, bool localized) noexcept
+ {
+ auto is_space_pred =
+ detail::make_is_space_predicate(ctx.locale(), localized);
+ return detail::read_until_pred_contiguous(ctx.range(), is_space_pred,
+ false, false)
+ .error();
+ }
+
+ /// @}
+
+ namespace detail {
+ template <typename T>
+ struct simple_integer_scanner {
+ template <typename CharT>
+ static expected<typename span<const CharT>::iterator> scan(
+ span<const CharT> buf,
+ T& val,
+ int base = 10,
+ uint16_t flags = 0);
+
+ template <typename CharT>
+ static expected<typename span<const CharT>::iterator> scan_lower(
+ span<const CharT> buf,
+ T& val,
+ int base = 10,
+ uint16_t flags = 0);
+ };
+ } // namespace detail
+
+ /**
+ * A very simple parser base class, which only accepts empty format string
+ * specifiers, e.g. `{}`, `{:}` or `{1:}`.
+ */
+ struct empty_parser : parser_base {
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ pctx.arg_begin();
+ if (SCN_UNLIKELY(!pctx)) {
+ return {error::invalid_format_string,
+ "Unexpected format string end"};
+ }
+ if (!pctx.check_arg_end()) {
+ return {error::invalid_format_string, "Expected argument end"};
+ }
+ pctx.arg_end();
+ return {};
+ }
+ };
+
+ /**
+ * Provides a framework for building a format string parser.
+ * Does not provide a `parse()` member function, so not a parser on to its
+ * own.
+ */
+ struct common_parser : parser_base {
+ static constexpr bool support_align_and_fill()
+ {
+ return true;
+ }
+
+ protected:
+ /**
+ * Parse the beginning of the argument.
+ * Returns `error::invalid_format_string` if `!pctx` (the format string
+ * ended)
+ */
+ template <typename ParseCtx>
+ error parse_common_begin(ParseCtx& pctx)
+ {
+ pctx.arg_begin();
+ if (SCN_UNLIKELY(!pctx)) {
+ return {error::invalid_format_string,
+ "Unexpected format string end"};
+ }
+ return {};
+ }
+
+ /**
+ * Returns `error::invalid_format_string` if the format string or the
+ * argument has ended.
+ */
+ template <typename ParseCtx>
+ error check_end(ParseCtx& pctx)
+ {
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+ return {};
+ }
+
+ /**
+ * Parse alignment, fill, width, and localization flags, and populate
+ * appropriate member variables.
+ *
+ * Returns `error::invalid_format_string` if an error occurred.
+ */
+ template <typename ParseCtx>
+ error parse_common_flags(ParseCtx& pctx)
+ {
+ SCN_EXPECT(check_end(pctx));
+ using char_type = typename ParseCtx::char_type;
+
+ auto ch = pctx.next_char();
+ auto next_char = [&]() -> error {
+ pctx.advance_char();
+ auto e = check_end(pctx);
+ if (!e) {
+ return e;
+ }
+ ch = pctx.next_char();
+ return {};
+ };
+ auto parse_number = [&](size_t& n) -> error {
+ SCN_EXPECT(pctx.locale().get_static().is_digit(ch));
+
+ auto it = pctx.begin();
+ for (; it != pctx.end(); ++it) {
+ if (!pctx.locale().get_static().is_digit(*it)) {
+ break;
+ }
+ }
+ auto buf = make_span(pctx.begin(), it);
+
+ auto s = detail::simple_integer_scanner<size_t>{};
+ auto res = s.scan(buf, n, 10);
+ if (!res) {
+ return res.error();
+ }
+
+ for (it = pctx.begin(); it != res.value();
+ pctx.advance_char(), it = pctx.begin()) {}
+ return {};
+ };
+
+ auto get_align_char = [&](char_type c) -> common_options_type {
+ if (c == detail::ascii_widen<char_type>('<')) {
+ return aligned_left;
+ }
+ if (c == detail::ascii_widen<char_type>('>')) {
+ return aligned_right;
+ }
+ if (c == detail::ascii_widen<char_type>('^')) {
+ return aligned_center;
+ }
+ return common_options_none;
+ };
+ auto parse_align = [&](common_options_type align, char_type fill) {
+ if (align != common_options_none) {
+ common_options |= align;
+ }
+ fill_char = static_cast<char32_t>(fill);
+ };
+
+ // align and fill
+ common_options_type align{};
+ bool align_set = false;
+ if (pctx.chars_left() > 1 &&
+ ch != detail::ascii_widen<char_type>('[')) {
+ const auto peek = pctx.peek_char();
+ align = get_align_char(peek);
+ if (align != common_options_none) {
+ // Arg is like "{:_x}", where _ is some fill character, and
+ // x is an alignment flag
+ // -> we have both alignment and fill
+ parse_align(align, ch);
+
+ auto e = next_char();
+ SCN_ENSURE(e);
+ if (!next_char()) {
+ return {};
+ }
+ align_set = true;
+ }
+ }
+ if (!align_set) {
+ align = get_align_char(ch);
+ if (align != common_options_none) {
+ // Arg is like "{:x}", where x is an alignment flag
+ // -> we have alignment with default fill (space ' ')
+ parse_align(align, detail::ascii_widen<char_type>(' '));
+ if (!next_char()) {
+ return {};
+ }
+ }
+ }
+
+ // digit -> width
+ if (pctx.locale().get_static().is_digit(ch)) {
+ common_options |= width_set;
+
+ size_t w{};
+ auto e = parse_number(w);
+ if (!e) {
+ return e;
+ }
+ field_width = w;
+ return {};
+ }
+ // L -> localized
+ if (ch == detail::ascii_widen<char_type>('L')) {
+ common_options |= localized;
+
+ if (!next_char()) {
+ return {};
+ }
+ }
+
+ return {};
+ }
+
+ /**
+ * Parse argument end.
+ *
+ * Returns `error::invalid_format_string` if argument end was not found.
+ */
+ template <typename ParseCtx>
+ error parse_common_end(ParseCtx& pctx)
+ {
+ if (!pctx || !pctx.check_arg_end()) {
+ return {error::invalid_format_string, "Expected argument end"};
+ }
+
+ pctx.arg_end();
+ return {};
+ }
+
+ /**
+ * A null callback to pass to `parse_common`, doing nothing and
+ * returning `error::good`.
+ */
+ template <typename ParseCtx>
+ static error null_type_cb(ParseCtx&, bool&)
+ {
+ return {};
+ }
+
+ public:
+ /**
+ * Parse a format string argument, using `parse_common_begin`,
+ * `parse_common_flags`, `parse_common_end`, and the supplied type
+ * flags.
+ *
+ * `type_options.size() == type_flags.size()` must be `true`.
+ * `pctx` must be valid, and must start at the format string argument
+ * specifiers, e.g. in the case of `"{1:foo}"` -> `pctx == "foo}"`
+ *
+ * \param pctx Format string to parse
+ * \param type_options A span of characters, where each character
+ * corresponds to a valid type flag. For example, for characters, this
+ * span would be \c ['c']
+ * \param type_flags A span of bools, where the values will be set to
+ * `true`, if a corresponding type flag from `type_options` was found.
+ * Should be initialized to all-`false`, as a `false` value will not be
+ * written.
+ * \param type_cb A callback to call, if none of the `type_options`
+ * matched. Must have the signature `(ParseCtx& pctx, bool& parsed) ->
+ * error`., where `parsed` is set to `true`, if the flag at
+ * `pctx.next_char()` was parsed and advanced past.
+ */
+ template <typename ParseCtx,
+ typename F,
+ typename CharT = typename ParseCtx::char_type>
+ error parse_common(ParseCtx& pctx,
+ span<const CharT> type_options,
+ span<bool> type_flags,
+ F&& type_cb)
+ {
+ SCN_EXPECT(type_options.size() == type_flags.size());
+
+ auto e = parse_common_begin(pctx);
+ if (!e) {
+ return e;
+ }
+
+ if (!pctx) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string"};
+ }
+ if (pctx.check_arg_end()) {
+ return {};
+ }
+
+ e = parse_common_flags(pctx);
+ if (!e) {
+ return e;
+ }
+
+ if (!pctx) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string"};
+ }
+ if (pctx.check_arg_end()) {
+ return {};
+ }
+
+ for (auto ch = pctx.next_char(); pctx && !pctx.check_arg_end();
+ ch = pctx.next_char()) {
+ bool parsed = false;
+ for (std::size_t i = 0; i < type_options.size() && !parsed;
+ ++i) {
+ if (ch == type_options[i]) {
+ if (SCN_UNLIKELY(type_flags[i])) {
+ return {error::invalid_format_string,
+ "Repeat flag in format string"};
+ }
+ type_flags[i] = true;
+ parsed = true;
+ }
+ }
+ if (parsed) {
+ pctx.advance_char();
+ if (!pctx || pctx.check_arg_end()) {
+ break;
+ }
+ continue;
+ }
+
+ e = type_cb(pctx, parsed);
+ if (!e) {
+ return e;
+ }
+ if (parsed) {
+ if (!pctx || pctx.check_arg_end()) {
+ break;
+ }
+ continue;
+ }
+ ch = pctx.next_char();
+
+ if (!parsed) {
+ return {error::invalid_format_string,
+ "Invalid character in format string"};
+ }
+ if (!pctx || pctx.check_arg_end()) {
+ break;
+ }
+ }
+
+ return parse_common_end(pctx);
+ }
+
+ void make_localized()
+ {
+ common_options |= localized;
+ }
+
+ /**
+ * Invoke `parse_common()` with default options (no type flags)
+ */
+ template <typename ParseCtx>
+ error parse_default(ParseCtx& pctx)
+ {
+ return parse_common(pctx, {}, {}, null_type_cb<ParseCtx>);
+ }
+
+ constexpr bool is_aligned_left() const noexcept
+ {
+ return (common_options & aligned_left) != 0 ||
+ (common_options & aligned_center) != 0;
+ }
+ constexpr bool is_aligned_right() const noexcept
+ {
+ return (common_options & aligned_right) != 0 ||
+ (common_options & aligned_center) != 0;
+ }
+ template <typename CharT>
+ constexpr CharT get_fill_char() const noexcept
+ {
+ return static_cast<CharT>(fill_char);
+ }
+
+ size_t field_width{0};
+ char32_t fill_char{0};
+ enum common_options_type : uint8_t {
+ common_options_none = 0,
+ localized = 1, // 'L',
+ aligned_left = 2, // '<'
+ aligned_right = 4, // '>'
+ aligned_center = 8, // '^'
+ width_set = 16, // width
+ common_options_all = 31,
+ };
+ uint8_t common_options{0};
+ };
+
+ /**
+ * Derives from `common_parser`, and implements `parse()` with
+ * `parse_default()`
+ */
+ struct common_parser_default : common_parser {
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ return parse_default(pctx);
+ }
+ };
+
+ namespace detail {
+ template <typename Context,
+ typename std::enable_if<
+ !Context::range_type::is_contiguous>::type* = nullptr>
+ error scan_alignment(Context& ctx,
+ typename Context::char_type fill) noexcept
+ {
+ while (true) {
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+
+ auto ch = read_code_unit(ctx.range());
+ if (SCN_UNLIKELY(!ch)) {
+ return ch.error();
+ }
+ if (ch.value() != fill) {
+ auto pb = putback_n(ctx.range(), 1);
+ if (SCN_UNLIKELY(!pb)) {
+ return pb;
+ }
+ break;
+ }
+
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+ return {};
+ }
+ template <typename Context,
+ typename std::enable_if<
+ Context::range_type::is_contiguous>::type* = nullptr>
+ error scan_alignment(Context& ctx,
+ typename Context::char_type fill) noexcept
+ {
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ const auto end = ctx.range().end();
+ for (auto it = ctx.range().begin(); it != end; ++it) {
+ if (*it != fill) {
+ ctx.range().advance_to(it);
+ return {};
+ }
+ }
+ ctx.range().advance_to(end);
+ return {};
+
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+
+ template <typename Scanner, typename = void>
+ struct scanner_supports_alignment : std::false_type {
+ };
+ template <typename Scanner>
+ struct scanner_supports_alignment<
+ Scanner,
+ typename std::enable_if<Scanner::support_align_and_fill()>::type>
+ : std::true_type {
+ };
+
+ template <typename Context, typename Scanner>
+ error skip_alignment(Context& ctx,
+ Scanner& scanner,
+ bool left,
+ std::true_type)
+ {
+ if (left && !scanner.is_aligned_left()) {
+ return {};
+ }
+ if (!left && !scanner.is_aligned_right()) {
+ return {};
+ }
+ return scan_alignment(
+ ctx,
+ scanner.template get_fill_char<typename Context::char_type>());
+ }
+ template <typename Context, typename Scanner>
+ error skip_alignment(Context&, Scanner&, bool, std::false_type)
+ {
+ return {};
+ }
+
+ /**
+ * Scan argument in `val`, from `ctx`, using `Scanner` and `pctx`.
+ *
+ * Parses `pctx` for `Scanner`, skips whitespace and alignment if
+ * necessary, and scans the argument into `val`.
+ */
+ template <typename Scanner,
+ typename T,
+ typename Context,
+ typename ParseCtx>
+ error visitor_boilerplate(T& val, Context& ctx, ParseCtx& pctx)
+ {
+ Scanner scanner;
+
+ auto err = pctx.parse(scanner);
+ if (!err) {
+ return err;
+ }
+
+ if (scanner.skip_preceding_whitespace()) {
+ err = skip_range_whitespace(ctx, false);
+ if (!err) {
+ return err;
+ }
+ }
+
+ err = skip_alignment(ctx, scanner, false,
+ scanner_supports_alignment<Scanner>{});
+ if (!err) {
+ return err;
+ }
+
+ err = scanner.scan(val, ctx);
+ if (!err) {
+ return err;
+ }
+
+ return skip_alignment(ctx, scanner, true,
+ scanner_supports_alignment<Scanner>{});
+ }
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif