Skip to content

Commit

Permalink
Revise scanner::parse error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
eliaskosunen committed Sep 30, 2024
1 parent c71c434 commit 5e548d0
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 64 deletions.
22 changes: 10 additions & 12 deletions include/scn/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -1466,15 +1466,15 @@ struct tm_format_checker {

template <typename T, typename CharT, typename ParseCtx>
constexpr auto chrono_parse_impl(ParseCtx& pctx,
std::basic_string_view<CharT>& fmt_str)
-> scan_expected<typename ParseCtx::iterator>
std::basic_string_view<CharT>& fmt_str) ->
typename ParseCtx::iterator
{
auto it = pctx.begin();
auto end = pctx.end();
if (it == end || *it == CharT{'}'}) {
return unexpected(scan_error{
scan_error::invalid_format_string,
"Format string without specifiers is not valid for this type"});
pctx.on_error(
"Format string without specifiers is not valid for this type");
return it;
}

auto checker = detail::tm_format_checker<T>{};
Expand All @@ -1483,7 +1483,8 @@ constexpr auto chrono_parse_impl(ParseCtx& pctx,
fmt_str = detail::make_string_view_from_iterators<CharT>(it, end);
}
if (auto e = checker.get_error(); SCN_UNLIKELY(!e)) {
return unexpected(e);
assert(e.code() == scan_error::invalid_format_string);
pctx.on_error(e.msg());
}
return end;
}
Expand All @@ -1495,8 +1496,7 @@ auto chrono_scan_impl(std::basic_string_view<CharT> fmt_str, T& t, Context& ctx)
template <typename CharT, typename T>
struct chrono_datetime_scanner {
template <typename ParseCtx>
constexpr auto parse(ParseCtx& pctx)
-> scan_expected<typename ParseCtx::iterator>
constexpr auto parse(ParseCtx& pctx) -> typename ParseCtx::iterator
{
return detail::chrono_parse_impl<T, CharT>(pctx, m_fmt_str);
}
Expand Down Expand Up @@ -1536,12 +1536,10 @@ struct chrono_component_scanner

public:
template <typename ParseCtx>
constexpr scan_expected<typename ParseCtx::iterator> parse(ParseCtx& pctx)
constexpr typename ParseCtx::iterator parse(ParseCtx& pctx)
{
if (pctx.begin() == pctx.end() || *pctx.begin() == CharT{'}'}) {
return unexpected(
scan_error{scan_error::invalid_format_string,
"Default format not supported for this type"});
pctx.on_error("Default format not supported for this type");
}
return base::parse(pctx);
}
Expand Down
14 changes: 12 additions & 2 deletions include/scn/fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,13 @@ SCN_GCC_POP
#define SCN_HAS_LIKELY_ATTR 0
#endif // SCN_STD >= 20

// Detect __attribute__((cold))
#if SCN_GCC || SCN_CLANG
#define SCN_COLD __attribute__((cold, noinline))
#else
#define SCN_COLD /* cold */
#endif

// Detect [[clang::trivial_abi]]
#if SCN_HAS_CPP_ATTRIBUTE(clang::trivial_abi)
#define SCN_HAS_TRIVIAL_ABI 1
Expand Down Expand Up @@ -1061,14 +1068,17 @@ struct scanner {
* `scanner`, while only overriding `scan()`, and keeping the same
* `parse()`, or at least delegating to it.
*
* To report errors, an exception derived from `std::exception` can be
* thrown, or `ParseContext::on_error` can be called.
*
* \return On success, an iterator pointing to the `}` character at the end
* of the replacement field in the format string.
* Will cause an error, if the returned iterator doesn't point to a `}`
* character.
*/
template <typename ParseContext>
constexpr auto parse(ParseContext& pctx)
-> expected<typename ParseContext::iterator, scan_error> = delete;
constexpr auto parse(ParseContext& pctx) ->
typename ParseContext::iterator = delete;

/**
* Scan a value of type `T` from `ctx` into `value`,
Expand Down
2 changes: 1 addition & 1 deletion include/scn/istream.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ using wrange_streambuf = basic_range_streambuf<wscan_context::range_type>;
template <typename CharT>
struct basic_istream_scanner {
template <typename ParseContext>
scan_expected<typename ParseContext::iterator> parse(ParseContext& ctx)
typename ParseContext::iterator parse(ParseContext& ctx)
{
return ctx.begin();
}
Expand Down
6 changes: 3 additions & 3 deletions include/scn/ranges.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ struct scanner<
}

template <typename ParseCtx>
constexpr scan_expected<typename ParseCtx::iterator> parse(ParseCtx& pctx)
constexpr typename ParseCtx::iterator parse(ParseCtx& pctx)
{
return pctx.begin();
}
Expand Down Expand Up @@ -415,7 +415,7 @@ class range_scanner<T,
}

template <typename ParseCtx>
constexpr scan_expected<typename ParseCtx::iterator> parse(ParseCtx& pctx)
constexpr typename ParseCtx::iterator parse(ParseCtx& pctx)
{
// TODO
return m_underlying.parse(pctx);
Expand Down Expand Up @@ -489,7 +489,7 @@ class range_default_scanner<Kind,
}

template <typename ParseCtx>
constexpr scan_expected<typename ParseCtx::iterator> parse(ParseCtx& pctx)
constexpr typename ParseCtx::iterator parse(ParseCtx& pctx)
{
return m_underlying.parse(pctx);
}
Expand Down
162 changes: 124 additions & 38 deletions include/scn/scan.h
Original file line number Diff line number Diff line change
Expand Up @@ -3662,9 +3662,32 @@ constexpr inline bool operator!=(enum scan_error::code a, scan_error b) noexcept

namespace detail {
// Intentionally not constexpr, to give out a compile-time error
scan_error handle_error(scan_error e);
SCN_COLD scan_error handle_error(scan_error e);
} // namespace detail

#if SCN_HAS_EXCEPTIONS
class scan_format_string_error : public std::runtime_error {
public:
explicit scan_format_string_error(const std::string& what_arg)
: runtime_error(what_arg)
{
}

explicit scan_format_string_error(const char* what_arg)
: runtime_error(what_arg)
{
}

template <std::size_t N>
explicit scan_format_string_error(const char (&what_arg)[N])
: runtime_error(what_arg), _internal_literal_msg(what_arg)
{
}

const char* _internal_literal_msg{nullptr};
};
#endif

/**
* An `expected<T, scan_error>`.
*
Expand Down Expand Up @@ -5069,7 +5092,32 @@ class arg_value {
auto& pctx_ref = *static_cast<parse_context_type*>(pctx);
auto& ctx_ref = *static_cast<context_type*>(ctx);

SCN_TRY_ERR(fmt_it, s.parse(pctx_ref));
#if SCN_HAS_EXCEPTIONS
auto fmt_it = pctx_ref.begin();
try {
fmt_it = s.parse(pctx_ref);
}
catch (const scan_format_string_error& ex) {
// scan_error takes a const char*.
// scan_format_string_error (or, actually, std::runtime_error)
// stores a reference-counted string,
// that will go out of scope here.
// We need to provide a const char* that will stay in scope.
// If scan_format_string_error was thrown with a string literal,
// use that, otherwise refer to a thread_local std::string
if (ex._internal_literal_msg) {
return {scan_error::invalid_format_string,
ex._internal_literal_msg};
}
thread_local std::string err_msg{ex.what()};
return {scan_error::invalid_format_string, err_msg.c_str()};
}
#else
auto fmt_it = s.parse(pctx_ref);
#endif
if (auto e = pctx_ref.get_error(); SCN_UNLIKELY(!e)) {
return e;
}
pctx_ref.advance_to(fmt_it);

SCN_TRY_ERR(it, s.scan(arg_ref, ctx_ref));
Expand Down Expand Up @@ -5718,16 +5766,22 @@ class basic_scan_parse_context {
do_check_arg_id(id);
}

constexpr scan_error on_error(const char* msg) const
scan_error on_error(const char* msg)
{
return m_error = detail::handle_error(
scan_error{scan_error::invalid_format_string, msg});
}

scan_error get_error()
{
return detail::handle_error(
scan_error{scan_error::invalid_format_string, msg});
return m_error;
}

protected:
constexpr void do_check_arg_id(size_t id);

std::basic_string_view<CharT> m_format;
scan_error m_error{};
int m_next_arg_id{0};
};

Expand Down Expand Up @@ -7843,23 +7897,47 @@ inline constexpr bool scanner_has_format_specs_member_v<
Scanner,
std::void_t<decltype(SCN_DECLVAL(Scanner&)._format_specs())>> = true;

template <typename T, typename Source, typename Ctx, typename ParseCtx>
constexpr typename ParseCtx::iterator parse_format_specs(ParseCtx& parse_ctx)
template <typename Scanner, typename ParseCtx>
using dt_scanner_parse =
decltype(SCN_DECLVAL(Scanner&).parse(SCN_DECLVAL(ParseCtx&)));
template <typename Scanner, typename T, typename Ctx>
using dt_scanner_scan = decltype(SCN_DECLVAL(const Scanner&)
.scan(SCN_DECLVAL(T&), SCN_DECLVAL(Ctx&)));

template <typename Scanner,
typename Source,
typename T,
typename Ctx,
typename ParseCtx>
constexpr typename ParseCtx::iterator parse_format_specs_impl(
ParseCtx& parse_ctx)
{
using char_type = typename ParseCtx::char_type;
using mapped_type = std::conditional_t<
mapped_type_constant<T, char_type>::value != arg_type::custom_type,
std::remove_reference_t<decltype(arg_mapper<char_type>().map(
SCN_DECLVAL(T&)))>,
T>;
auto s = typename Ctx::template scanner_type<mapped_type>{};
auto it = s.parse(parse_ctx)
.transform_error([&](scan_error err) constexpr {
parse_ctx.on_error(err.msg());
return err;
})
.value_or(parse_ctx.end());
if constexpr (scanner_has_format_specs_member_v<decltype(s)>) {
static_assert(
std::is_default_constructible_v<Scanner>,
"Specializations of scn::scanner must be default constructible");
static_assert(mp_valid<dt_scanner_parse, Scanner, ParseCtx>::value,
"Specializations of scn::scanner must have a "
"parse(ParseContext&) member function.");
static_assert(
std::is_same_v<mp_eval_or<void, dt_scanner_parse, Scanner, ParseCtx>,
typename ParseCtx::iterator>,
"scn::scanner::parse(ParseContext&) must return "
"ParseContext::iterator. To report an error from scanner::parse, "
"either throw an exception derived from scn::scan_format_string_error, "
"or call ParseContext::on_error.");
static_assert(mp_valid<dt_scanner_scan, Scanner, T, Ctx>::value,
"Specializations of scn::scanner must have a "
"scan(T&, Context&) const member function.");
static_assert(
std::is_same_v<mp_eval_or<void, dt_scanner_scan, Scanner, T, Ctx>,
scan_expected<typename Ctx::iterator>>,
"scn::scanner::scan(T&, Context&) must return "
"scan_expected<Context::iterator>.");

auto s = Scanner{};
auto it = s.parse(parse_ctx);

if constexpr (scanner_has_format_specs_member_v<Scanner>) {
auto& specs = s._format_specs();
if ((specs.type == presentation_type::regex ||
specs.type == presentation_type::regex_escaped) &&
Expand All @@ -7872,6 +7950,20 @@ constexpr typename ParseCtx::iterator parse_format_specs(ParseCtx& parse_ctx)
return it;
}

template <typename T, typename Source, typename Ctx, typename ParseCtx>
constexpr typename ParseCtx::iterator parse_format_specs(ParseCtx& parse_ctx)
{
using char_type = typename ParseCtx::char_type;
using mapped_type = std::conditional_t<
mapped_type_constant<T, char_type>::value != arg_type::custom_type,
std::remove_reference_t<decltype(arg_mapper<char_type>().map(
SCN_DECLVAL(T&)))>,
T>;
using scanner_type = typename Ctx::template scanner_type<mapped_type>;
return parse_format_specs_impl<scanner_type, Source, T, Ctx, ParseCtx>(
parse_ctx);
}

template <typename CharT, typename Source, typename... Args>
class format_string_checker {
public:
Expand All @@ -7892,7 +7984,7 @@ class format_string_checker {
{
}

constexpr void on_literal_text(const CharT* begin, const CharT* end) const
constexpr void on_literal_text(const CharT* begin, const CharT* end)
{
// TODO: Do we want to validate Unicode in format strings?
// We're dealing with text, so we probably do.
Expand Down Expand Up @@ -7949,7 +8041,7 @@ class format_string_checker {
return id < num_args ? m_parse_funcs[id](m_parse_context) : begin;
}

constexpr void check_args_exhausted() const
constexpr void check_args_exhausted()
{
if (num_args == 0) {
return;
Expand All @@ -7961,7 +8053,7 @@ class format_string_checker {
}
}

void on_error(const char* msg) const
void on_error(const char* msg)
{
SCN_UNLIKELY_ATTR
m_parse_context.on_error(msg);
Expand Down Expand Up @@ -8314,8 +8406,9 @@ class basic_scan_context : public detail::scan_context_base<

namespace detail {
template <typename T, typename ParseCtx>
constexpr scan_expected<typename ParseCtx::iterator>
scanner_parse_for_builtin_type(ParseCtx& pctx, format_specs& specs);
constexpr typename ParseCtx::iterator scanner_parse_for_builtin_type(
ParseCtx& pctx,
format_specs& specs);

template <typename T, typename Context>
scan_expected<typename Context::iterator>
Expand All @@ -8338,8 +8431,7 @@ struct scanner<T,
detail::arg_type::custom_type &&
!detail::is_type_disabled<T>>> {
template <typename ParseCtx>
constexpr auto parse(ParseCtx& pctx)
-> scan_expected<typename ParseCtx::iterator>
constexpr auto parse(ParseCtx& pctx) -> typename ParseCtx::iterator
{
return detail::scanner_parse_for_builtin_type<T>(pctx, m_specs);
}
Expand All @@ -8361,8 +8453,9 @@ struct scanner<T,

namespace detail {
template <typename T, typename ParseCtx>
constexpr scan_expected<typename ParseCtx::iterator>
scanner_parse_for_builtin_type(ParseCtx& pctx, format_specs& specs)
constexpr typename ParseCtx::iterator scanner_parse_for_builtin_type(
ParseCtx& pctx,
format_specs& specs)
{
using char_type = typename ParseCtx::char_type;

Expand All @@ -8379,9 +8472,6 @@ scanner_parse_for_builtin_type(ParseCtx& pctx, format_specs& specs)

const auto it =
detail::parse_format_specs(to_address(begin), to_address(end), checker);
if (auto e = checker.get_error(); SCN_UNLIKELY(!e)) {
return unexpected(e);
}

switch (type) {
case arg_type::none_type:
Expand Down Expand Up @@ -8445,11 +8535,7 @@ scanner_parse_for_builtin_type(ParseCtx& pctx, format_specs& specs)
SCN_CLANG_POP
}

if (auto e = checker.get_error(); SCN_UNLIKELY(!e)) {
return unexpected(e);
}

return {it};
return it;
}
} // namespace detail

Expand Down
1 change: 1 addition & 0 deletions src/scn/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ template auto locale_ref::get() const -> std::locale;
namespace detail {
scan_error handle_error(scan_error e)
{
SCN_UNLIKELY_ATTR
return e;
}
} // namespace detail
Expand Down
Loading

0 comments on commit 5e548d0

Please sign in to comment.