Files
kaizen/test/scan.h
Simone 5ff285a9ed Squashed 'external/fmt/' changes from 3b70966df..093b39ca5
093b39ca5 Update docs for meson (#4291)
2c3a5698e Simplify a copying the fill from basic_specs
fc1b0f348 Clarify use of FMT_THROW in a comment
1d066890c Resolve C4702 unreachable code warnings
dad323751 Fix a bug when copying the fill from basic_specs
880e1494d Improve xchar support for std::bitset formatter
e3ddede6c Update version
e9ec4fdc8 Bump version
feb72126b Readd FMT_NO_UNIQUE_ADDRESS
8d517e54c Update changelog
563fc74ae Update changelog
3e04222d5 Restore ABI compatibility with 11.0.2
853df39d0 Mention compile-time formatting
11742a09c Clarify that format_string should be used instead of fstring
da24fac10 Document fstring
5fa4bdd75 Define CMake components to allow docs to be installed separately (#4276)
3c8aad8df Update the release script
0e8aad961 Update version
debe784aa Update changelog
f6d112567 Update changelog
73d0d3f75 Fix github API call
08f60f1ef Update changelog
faf3f8408 Bump version
f3a41441d Replace requests with urllib
3f33cb21d Update changelog
b07a90386 Update changelog
a6fba5177 Update changelog
25e292998 Update changelog
00ab2e98b Update changelog
a3ef285ae Always inline const_check to improve debug codegen in clang
28d1abc9d Update changelog
90704b9ef Update changelog
86dae01c2 Fix compatibility with older versions of VS (#4271)
d8a79eafd Document formatting of bit-fields and fields of packed structs
7c3d0152e Use the _MSVC_STL_UPDATE macro to detect STL (#4267)
7c50da538 Allow getting size of dynamic format arg store (#4270)
873670ba3 Make parameter basic_memory_buffer<char, SIZE>& buf of to_string const
735d4cc05 Update changelog
141380172 Allow disabling <filesystem> by define FMT_CPP_LIB_FILESYSTEM=0 (#4259)
4302d7429 Update changelog
0f51ea79d Update changelog
9600fee02 Include <filesystem> only if FMT_CPP_LIB_FILESYSTEM is set (#4258)
47a66c5ec Bump msys2/setup-msys2 from 2.24.0 to 2.25.0 (#4250)
385c01dc7 Allow bit_cast to work for 80bit long double (#4246)
df249d8ad Remove an old workaround
dfad80d1c Remove an old workaround
536cabd56 Export all range join overloads (#4239)
b1a054706 Remove more MSVC 2015 workarounds and fix string_view checks
bfd95392c Remove MSVC 2015 workaround
9ced61bca Replace std::forward for clang-tidy (#4236)
75e5be6ad Sort specifiers
a169d7fa4 Fix chrono formatting syntax doc (#4235)
a6c45dfea Fix modular build
a35389b3c Corrently handle buffer flush
5a3576acc Implement fmt::join for tuple-like objects (#4230)
542600013 Suppress MSVC warnings "C4127: conditional expression is constant" by used const_check (#4233)
720da57ba Remove reference to unused intrinsic
680db66c3 Explicitly export symbols from detail
56ce41ef6 Remove initializer_list dependency
cf50e4d6a Fix const[expr] in context API
6580d7b80 Cleanup the format API
7e73566ce Minor cleanup
8523dba2d Make constexpr precede explicit consistently
e3d3b24fc Minor cleanup
1521bba70 Use consistent types for argument count
00649552a Bump github/codeql-action from 3.26.6 to 3.27.0 (#4223)
4b8e2838f More cleanup
7d4662f7a Remove FMT_BUILTIN_CTZ
27110bc47 Minor cleanup
68f315376 Fix narrowing conversion warning in struct fstring (#4210)
168df9a06 Implement fmt::format_to into std::vector<char> (#4211)
4daa3d591 Fix error: cannot use 'try' with exceptions disabled in Win LLVM Clang (#4208)
e9eaa27e5 Add std::exception to the docs
2b6a786e3 Use standard context in print
a16ff5787 Add support for code units > 0xFFFF in fill
601be1cbe Add support for code units > 0xFFFF in fill
58c185b63 Changing type of data_ to size_t to avoid compilation warnings (#4200)
a0a9ba2af Fix hashes
cc2ba8f9e Cleanup cifuzz action
a18d42b20 Simplify lint (#4197)
4046f9727 Fix -Wmissing-noreturn warning (#4194)
6bdc12a19 detail_exported -> detail
786a4b096 Cleanup fixed_string
2cb3b7c64 Update README.md
e9cba6905 Update README.md
02537548f Cleanup an example
c68c5fa7c Test FMT_BUILTIN_TYPES
22701d5f6 Address build failures when using Tip-of-Tree clang. (#4187)
e62c41ffb Conform `std::iterator_traits<fmt::appender>` to [iterator.traits]/1 (#4185)
18792893d Silencing Wextra-semi warning (#4188)
c90bc9186 Bump actions/checkout from 4.1.6 to 4.2.0 (#4182)
c95722ad6 Improve naming consistency
db06b0df8 Use countl_zero in bigint
b9ec48d9c Cleanup bigint
3faf6f181 Add min_of/max_of
d64b100a3 Relax constexpr
ff9ee0461 Fix handling FMT_BUILTIN_TYPES
1c5883bef Test nondeterministic conversion to format string
cacc3108c Don't assume repeated evaluation of string literal produce the same pointer
fade652ad Require clang >=15 for _BitInt support (#4176)
96dca569a Module linkage fixes for shared build (#4169)
891c9a73a Cleanup format API
9282222b7 Export more
e5b20ff0d Deprecate detail::locale_ref
ff9222354 Simplify locale handling
80c4d42c6 Cleanup format.h

git-subtree-dir: external/fmt
git-subtree-split: 093b39ca5eea129b111060839602bcfaf295125a
2025-01-07 15:10:49 +00:00

670 lines
19 KiB
C++

// Formatting library for C++ - scanning API proof of concept
//
// Copyright (c) 2019 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include <array>
#include <cassert>
#include <climits>
#include <tuple>
#include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE
namespace detail {
inline auto is_whitespace(char c) -> bool { return c == ' ' || c == '\n'; }
// If c is a hex digit returns its numeric value, otherwise -1.
inline auto to_hex_digit(char c) -> int {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
struct maybe_contiguous_range {
const char* begin;
const char* end;
explicit operator bool() const { return begin != nullptr; }
};
class scan_buffer {
private:
const char* ptr_;
const char* end_;
bool contiguous_;
protected:
scan_buffer(const char* ptr, const char* end, bool contiguous)
: ptr_(ptr), end_(end), contiguous_(contiguous) {}
~scan_buffer() = default;
void set(span<const char> buf) {
ptr_ = buf.data;
end_ = buf.data + buf.size;
}
auto ptr() const -> const char* { return ptr_; }
public:
scan_buffer(const scan_buffer&) = delete;
void operator=(const scan_buffer&) = delete;
// Fills the buffer with more input if available.
virtual void consume() = 0;
class sentinel {};
class iterator {
private:
const char** ptr_;
scan_buffer* buf_; // This could be merged with ptr_.
char value_;
static auto get_sentinel() -> const char** {
static const char* ptr = nullptr;
return &ptr;
}
friend class scan_buffer;
friend auto operator==(iterator lhs, sentinel) -> bool {
return *lhs.ptr_ == nullptr;
}
friend auto operator!=(iterator lhs, sentinel) -> bool {
return *lhs.ptr_ != nullptr;
}
iterator(scan_buffer* buf) : buf_(buf) {
if (buf->ptr_ == buf->end_) {
ptr_ = get_sentinel();
return;
}
ptr_ = &buf->ptr_;
value_ = *buf->ptr_;
}
friend scan_buffer& get_buffer(iterator it) { return *it.buf_; }
public:
iterator() : ptr_(get_sentinel()), buf_(nullptr) {}
auto operator++() -> iterator& {
if (!buf_->try_consume()) ptr_ = get_sentinel();
value_ = *buf_->ptr_;
return *this;
}
auto operator++(int) -> iterator {
iterator copy = *this;
++*this;
return copy;
}
auto operator*() const -> char { return value_; }
auto base() const -> const char* { return buf_->ptr_; }
friend auto to_contiguous(iterator it) -> maybe_contiguous_range;
friend auto advance(iterator it, size_t n) -> iterator;
};
friend auto to_contiguous(iterator it) -> maybe_contiguous_range {
if (it.buf_->is_contiguous()) return {it.buf_->ptr_, it.buf_->end_};
return {nullptr, nullptr};
}
friend auto advance(iterator it, size_t n) -> iterator {
FMT_ASSERT(it.buf_->is_contiguous(), "");
const char*& ptr = it.buf_->ptr_;
ptr += n;
it.value_ = *ptr;
if (ptr == it.buf_->end_) it.ptr_ = iterator::get_sentinel();
return it;
}
auto begin() -> iterator { return this; }
auto end() -> sentinel { return {}; }
auto is_contiguous() const -> bool { return contiguous_; }
// Tries consuming a single code unit. Returns true iff there is more input.
auto try_consume() -> bool {
FMT_ASSERT(ptr_ != end_, "");
++ptr_;
if (ptr_ != end_) return true;
consume();
return ptr_ != end_;
}
};
using scan_iterator = scan_buffer::iterator;
using scan_sentinel = scan_buffer::sentinel;
class string_scan_buffer final : public scan_buffer {
private:
void consume() override {}
public:
explicit string_scan_buffer(string_view s)
: scan_buffer(s.begin(), s.end(), true) {}
};
class file_scan_buffer final : public scan_buffer {
private:
template <typename F, FMT_ENABLE_IF(sizeof(F::_IO_read_ptr) != 0 &&
!FMT_USE_FALLBACK_FILE)>
static auto get_file(F* f, int) -> glibc_file<F> {
return f;
}
template <typename F,
FMT_ENABLE_IF(sizeof(F::_p) != 0 && !FMT_USE_FALLBACK_FILE)>
static auto get_file(F* f, int) -> apple_file<F> {
return f;
}
static auto get_file(FILE* f, ...) -> fallback_file<FILE> { return f; }
decltype(get_file(static_cast<FILE*>(nullptr), 0)) file_;
// Fills the buffer if it is empty.
void fill() {
span<const char> buf = file_.get_read_buffer();
if (buf.size == 0) {
int c = file_.get();
// Put the character back since we are only filling the buffer.
if (c != EOF) file_.unget(static_cast<char>(c));
buf = file_.get_read_buffer();
}
set(buf);
}
void consume() override {
// Consume the current buffer content.
size_t n = to_unsigned(ptr() - file_.get_read_buffer().data);
for (size_t i = 0; i != n; ++i) file_.get();
fill();
}
public:
explicit file_scan_buffer(FILE* f)
: scan_buffer(nullptr, nullptr, false), file_(f) {
flockfile(f);
fill();
}
~file_scan_buffer() {
FILE* f = file_;
funlockfile(f);
}
};
} // namespace detail
template <typename T, typename Char = char> struct scanner {
// A deleted default constructor indicates a disabled scanner.
scanner() = delete;
};
class scan_parse_context {
private:
string_view format_;
public:
using iterator = string_view::iterator;
FMT_CONSTEXPR explicit scan_parse_context(string_view format)
: format_(format) {}
FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); }
FMT_CONSTEXPR auto end() const -> iterator { return format_.end(); }
void advance_to(iterator it) {
format_.remove_prefix(detail::to_unsigned(it - begin()));
}
};
namespace detail {
enum class scan_type {
none_type,
int_type,
uint_type,
long_long_type,
ulong_long_type,
string_type,
string_view_type,
custom_type
};
template <typename Context> struct custom_scan_arg {
void* value;
void (*scan)(void* arg, scan_parse_context& parse_ctx, Context& ctx);
};
} // namespace detail
// A scan argument. Context is a template parameter for the compiled API where
// output can be unbuffered.
template <typename Context> class basic_scan_arg {
private:
using scan_type = detail::scan_type;
scan_type type_;
union {
int* int_value_;
unsigned* uint_value_;
long long* long_long_value_;
unsigned long long* ulong_long_value_;
std::string* string_;
string_view* string_view_;
detail::custom_scan_arg<Context> custom_;
// TODO: more types
};
template <typename T>
static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx,
Context& ctx) {
auto s = scanner<T>();
parse_ctx.advance_to(s.parse(parse_ctx));
ctx.advance_to(s.scan(*static_cast<T*>(arg), ctx));
}
public:
FMT_CONSTEXPR basic_scan_arg()
: type_(scan_type::none_type), int_value_(nullptr) {}
FMT_CONSTEXPR basic_scan_arg(int& value)
: type_(scan_type::int_type), int_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(unsigned& value)
: type_(scan_type::uint_type), uint_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(long long& value)
: type_(scan_type::long_long_type), long_long_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(unsigned long long& value)
: type_(scan_type::ulong_long_type), ulong_long_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(std::string& value)
: type_(scan_type::string_type), string_(&value) {}
FMT_CONSTEXPR basic_scan_arg(string_view& value)
: type_(scan_type::string_view_type), string_view_(&value) {}
template <typename T>
FMT_CONSTEXPR basic_scan_arg(T& value) : type_(scan_type::custom_type) {
custom_.value = &value;
custom_.scan = scan_custom_arg<T>;
}
constexpr explicit operator bool() const noexcept {
return type_ != scan_type::none_type;
}
auto type() const -> detail::scan_type { return type_; }
template <typename Visitor>
auto visit(Visitor&& vis) -> decltype(vis(monostate())) {
switch (type_) {
case scan_type::none_type:
break;
case scan_type::int_type:
return vis(*int_value_);
case scan_type::uint_type:
return vis(*uint_value_);
case scan_type::long_long_type:
return vis(*long_long_value_);
case scan_type::ulong_long_type:
return vis(*ulong_long_value_);
case scan_type::string_type:
return vis(*string_);
case scan_type::string_view_type:
return vis(*string_view_);
case scan_type::custom_type:
break;
}
return vis(monostate());
}
auto scan_custom(const char* parse_begin, scan_parse_context& parse_ctx,
Context& ctx) const -> bool {
if (type_ != scan_type::custom_type) return false;
parse_ctx.advance_to(parse_begin);
custom_.scan(custom_.value, parse_ctx, ctx);
return true;
}
};
class scan_context;
using scan_arg = basic_scan_arg<scan_context>;
struct scan_args {
int size;
const scan_arg* data;
template <size_t N>
FMT_CONSTEXPR scan_args(const std::array<scan_arg, N>& store)
: size(N), data(store.data()) {
static_assert(N < INT_MAX, "too many arguments");
}
};
class scan_context {
private:
detail::scan_buffer& buf_;
scan_args args_;
public:
using iterator = detail::scan_iterator;
using sentinel = detail::scan_sentinel;
FMT_CONSTEXPR explicit scan_context(detail::scan_buffer& buf, scan_args args)
: buf_(buf), args_(args) {}
FMT_CONSTEXPR auto arg(int id) const -> scan_arg {
return id < args_.size ? args_.data[id] : scan_arg();
}
auto begin() const -> iterator { return buf_.begin(); }
auto end() const -> sentinel { return {}; }
void advance_to(iterator) { buf_.consume(); }
};
namespace detail {
const char* parse_scan_specs(const char* begin, const char* end,
format_specs& specs, scan_type) {
while (begin != end) {
switch (to_ascii(*begin)) {
// TODO: parse more scan format specifiers
case 'x':
specs.set_type(presentation_type::hex);
++begin;
break;
case '}':
return begin;
}
}
return begin;
}
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
auto read(scan_iterator it, T& value) -> scan_iterator {
if (it == scan_sentinel()) return it;
char c = *it;
if (c < '0' || c > '9') report_error("invalid input");
int num_digits = 0;
T n = 0, prev = 0;
char prev_digit = c;
do {
prev = n;
n = n * 10 + static_cast<unsigned>(c - '0');
prev_digit = c;
c = *++it;
++num_digits;
if (c < '0' || c > '9') break;
} while (it != scan_sentinel());
// Check overflow.
if (num_digits <= std::numeric_limits<int>::digits10) {
value = n;
return it;
}
unsigned max = to_unsigned((std::numeric_limits<int>::max)());
if (num_digits == std::numeric_limits<int>::digits10 + 1 &&
prev * 10ull + unsigned(prev_digit - '0') <= max) {
value = n;
} else {
report_error("number is too big");
}
return it;
}
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
auto read_hex(scan_iterator it, T& value) -> scan_iterator {
if (it == scan_sentinel()) return it;
int digit = to_hex_digit(*it);
if (digit < 0) report_error("invalid input");
int num_digits = 0;
T n = 0;
do {
n = (n << 4) + static_cast<unsigned>(digit);
++num_digits;
digit = to_hex_digit(*++it);
if (digit < 0) break;
} while (it != scan_sentinel());
// Check overflow.
if (num_digits <= (std::numeric_limits<T>::digits >> 2))
value = n;
else
report_error("number is too big");
return it;
}
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
auto read(scan_iterator it, T& value, const format_specs& specs)
-> scan_iterator {
if (specs.type() == presentation_type::hex) return read_hex(it, value);
return read(it, value);
}
template <typename T, FMT_ENABLE_IF(std::is_signed<T>::value)>
auto read(scan_iterator it, T& value, const format_specs& specs = {})
-> scan_iterator {
bool negative = it != scan_sentinel() && *it == '-';
if (negative) {
++it;
if (it == scan_sentinel()) report_error("invalid input");
}
using unsigned_type = typename std::make_unsigned<T>::type;
unsigned_type abs_value = 0;
it = read(it, abs_value, specs);
auto n = static_cast<T>(abs_value);
value = negative ? -n : n;
return it;
}
auto read(scan_iterator it, std::string& value, const format_specs& = {})
-> scan_iterator {
while (it != scan_sentinel() && *it != ' ') value.push_back(*it++);
return it;
}
auto read(scan_iterator it, string_view& value, const format_specs& = {})
-> scan_iterator {
auto range = to_contiguous(it);
// This could also be checked at compile time in scan.
if (!range) report_error("string_view requires contiguous input");
auto p = range.begin;
while (p != range.end && *p != ' ') ++p;
size_t size = to_unsigned(p - range.begin);
value = {range.begin, size};
return advance(it, size);
}
auto read(scan_iterator it, monostate, const format_specs& = {})
-> scan_iterator {
return it;
}
// An argument scanner that uses the default format, e.g. decimal for integers.
struct default_arg_scanner {
scan_iterator it;
template <typename T> FMT_INLINE auto operator()(T&& value) -> scan_iterator {
return read(it, value);
}
};
// An argument scanner with format specifiers.
struct arg_scanner {
scan_iterator it;
const format_specs& specs;
template <typename T> auto operator()(T&& value) -> scan_iterator {
return read(it, value, specs);
}
};
struct scan_handler {
private:
scan_parse_context parse_ctx_;
scan_context scan_ctx_;
int next_arg_id_;
using sentinel = scan_buffer::sentinel;
public:
FMT_CONSTEXPR scan_handler(string_view format, scan_buffer& buf,
scan_args args)
: parse_ctx_(format), scan_ctx_(buf, args), next_arg_id_(0) {}
auto pos() const -> scan_buffer::iterator { return scan_ctx_.begin(); }
void on_text(const char* begin, const char* end) {
if (begin == end) return;
auto it = scan_ctx_.begin();
for (; begin != end; ++begin, ++it) {
if (it == sentinel() || *begin != *it) on_error("invalid input");
}
scan_ctx_.advance_to(it);
}
FMT_CONSTEXPR auto on_arg_id() -> int { return on_arg_id(next_arg_id_++); }
FMT_CONSTEXPR auto on_arg_id(int id) -> int {
if (!scan_ctx_.arg(id)) on_error("argument index out of range");
return id;
}
FMT_CONSTEXPR auto on_arg_id(string_view id) -> int {
if (id.data()) on_error("invalid format");
return 0;
}
void on_replacement_field(int arg_id, const char* begin) {
scan_arg arg = scan_ctx_.arg(arg_id);
if (arg.scan_custom(begin, parse_ctx_, scan_ctx_)) return;
auto it = scan_ctx_.begin();
while (it != sentinel() && is_whitespace(*it)) ++it;
scan_ctx_.advance_to(arg.visit(default_arg_scanner{it}));
}
auto on_format_specs(int arg_id, const char* begin, const char* end) -> const
char* {
scan_arg arg = scan_ctx_.arg(arg_id);
if (arg.scan_custom(begin, parse_ctx_, scan_ctx_))
return parse_ctx_.begin();
auto specs = format_specs();
begin = parse_scan_specs(begin, end, specs, arg.type());
if (begin == end || *begin != '}') on_error("missing '}' in format string");
scan_ctx_.advance_to(arg.visit(arg_scanner{scan_ctx_.begin(), specs}));
return begin;
}
FMT_NORETURN void on_error(const char* message) { report_error(message); }
};
void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) {
auto h = detail::scan_handler(fmt, buf, args);
detail::parse_format_string(fmt, h);
}
template <size_t I, typename... T, FMT_ENABLE_IF(I == sizeof...(T))>
void make_args(std::array<scan_arg, sizeof...(T)>&, std::tuple<T...>&) {}
template <size_t I, typename... T, FMT_ENABLE_IF(I < sizeof...(T))>
void make_args(std::array<scan_arg, sizeof...(T)>& args,
std::tuple<T...>& values) {
using element_type = typename std::tuple_element<I, std::tuple<T...>>::type;
static_assert(std::is_same<remove_cvref_t<element_type>, element_type>::value,
"");
args[I] = std::get<I>(values);
make_args<I + 1>(args, values);
}
} // namespace detail
template <typename Range, typename... T> class scan_data {
private:
std::tuple<T...> values_;
Range range_;
public:
scan_data() = default;
scan_data(T... values) : values_(std::move(values)...) {}
auto value() const -> decltype(std::get<0>(values_)) {
return std::get<0>(values_);
}
auto values() const -> const std::tuple<T...>& { return values_; }
auto make_args() -> std::array<scan_arg, sizeof...(T)> {
auto args = std::array<scan_arg, sizeof...(T)>();
detail::make_args<0>(args, values_);
return args;
}
auto range() const -> Range { return range_; }
auto begin() const -> decltype(range_.begin()) { return range_.begin(); }
auto end() const -> decltype(range_.end()) { return range_.end(); }
};
template <typename... T>
auto make_scan_args(T&... args) -> std::array<scan_arg, sizeof...(T)> {
return {{args...}};
}
class scan_error {};
// A rudimentary version of std::expected for testing the API shape.
template <typename T, typename E> class expected {
private:
T value_;
bool has_value_ = true;
public:
expected(T value) : value_(std::move(value)) {}
explicit operator bool() const { return has_value_; }
auto operator->() const -> const T* { return &value_; }
auto error() -> E const { return E(); }
};
template <typename Range, typename... T>
using scan_result = expected<scan_data<Range, T...>, scan_error>;
auto vscan(string_view input, string_view fmt, scan_args args)
-> string_view::iterator {
auto&& buf = detail::string_scan_buffer(input);
detail::vscan(buf, fmt, args);
return input.begin() + (buf.begin().base() - input.data());
}
// Scans the input and stores the results (in)to args.
template <typename... T>
auto scan_to(string_view input, string_view fmt, T&... args)
-> string_view::iterator {
return vscan(input, fmt, make_scan_args(args...));
}
template <typename... T>
auto scan(string_view input, string_view fmt)
-> scan_result<string_view, T...> {
auto data = scan_data<string_view, T...>();
vscan(input, fmt, data.make_args());
return data;
}
template <typename Range, typename... T,
FMT_ENABLE_IF(!std::is_convertible<Range, string_view>::value)>
auto scan_to(Range&& input, string_view fmt, T&... args)
-> decltype(std::begin(input)) {
auto it = std::begin(input);
detail::vscan(get_buffer(it), fmt, make_scan_args(args...));
return it;
}
template <typename... T>
auto scan_to(FILE* f, string_view fmt, T&... args) -> bool {
auto&& buf = detail::file_scan_buffer(f);
detail::vscan(buf, fmt, make_scan_args(args...));
return buf.begin() != buf.end();
}
FMT_END_NAMESPACE