Skip to content

Commit

Permalink
Add invalid_literal, invalid_fill, and length_too_short
Browse files Browse the repository at this point in the history
  • Loading branch information
eliaskosunen committed Oct 15, 2024
1 parent 452d6a0 commit 65beffa
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 39 deletions.
24 changes: 20 additions & 4 deletions include/scn/scan.h
Original file line number Diff line number Diff line change
Expand Up @@ -3655,15 +3655,28 @@ class SCN_TRIVIAL_ABI scan_error {
public:
/// Error code
enum code {
/// EOF
/// Input ended unexpectedly.
end_of_input,

/// Format string was invalid
/// Format string was invalid.
/// Often a compile-time error, if supported or enabled.
invalid_format_string,

/// Scanned value was invalid for given type.
/// Scanned value was invalid for given type,
/// or a value of the given couldn't be scanned.
invalid_scanned_value,

/// Literal character specified in format string not found in source.
invalid_literal,

/// Too many fill characters scanned,
/// field precision (max width) exceeded.
invalid_fill,

/// Scanned field width was shorter than
/// what was specified as the minimum field width.
length_too_short,

/// Source range is in an invalid state,
/// failed to continue reading.
invalid_source_state,
Expand Down Expand Up @@ -3719,6 +3732,9 @@ class SCN_TRIVIAL_ABI scan_error {
case end_of_input:
case invalid_format_string:
case invalid_scanned_value:
case invalid_literal:
case invalid_fill:
case length_too_short:
return std::errc::invalid_argument;
case invalid_source_state:
return std::errc::io_error;
Expand Down Expand Up @@ -9378,7 +9394,7 @@ SCN_NODISCARD auto input(scan_format_string<std::FILE*, Args...> format)
std::tuple<Args...>{});
auto err = vinput(format, make_scan_args(result->values()));
if (SCN_UNLIKELY(!err)) {
result = unexpected(err);
result = unexpected(err.error());
}
return result;
}
Expand Down
30 changes: 20 additions & 10 deletions src/scn/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1773,7 +1773,8 @@ struct format_handler_base {
: (1ull << args_count_lower64) - 1;

if (visited_args_lower64 != mask) {
return on_error("Argument list not exhausted");
return on_error({scan_error::invalid_format_string,
"Argument list not exhausted"});
}
}

Expand All @@ -1785,14 +1786,16 @@ struct format_handler_base {
for (auto it = visited_args_upper.begin();
it != visited_args_upper.end() - 1; ++it) {
if (*it != std::numeric_limits<uint8_t>::max()) {
return on_error("Argument list not exhausted");
return on_error({scan_error::invalid_format_string,
"Argument list not exhausted"});
}
last_args_count -= 8;
}

const auto mask = static_cast<uint8_t>(1u << last_args_count) - 1;
if (visited_args_upper.back() != mask) {
return on_error("Argument list not exhausted");
return on_error({scan_error::invalid_format_string,
"Argument list not exhausted"});
}
}

Expand All @@ -1814,7 +1817,8 @@ struct format_handler_base {
SCN_NODISCARD bool has_arg_been_visited(size_t id)
{
if (SCN_UNLIKELY(id >= args_count)) {
on_error("Invalid out-of-range argument ID");
on_error({scan_error::invalid_format_string,
"Argument ID out-of-range"});
return false;
}

Expand All @@ -1829,12 +1833,14 @@ struct format_handler_base {
void set_arg_as_visited(size_t id)
{
if (SCN_UNLIKELY(id >= args_count)) {
on_error("Invalid out-of-range argument ID");
on_error({scan_error::invalid_format_string,
"Argument ID out-of-range"});
return;
}

if (SCN_UNLIKELY(has_arg_been_visited(id))) {
return on_error("Argument with this ID has already been scanned");
on_error({scan_error::invalid_format_string,
"Argument with this ID has already been scanned"});
}

if (SCN_LIKELY(id < 64)) {
Expand Down Expand Up @@ -1944,14 +1950,16 @@ struct format_handler : format_handler_base {
auto it = get_ctx().begin();
if (impl::is_range_eof(it, get_ctx().end())) {
SCN_UNLIKELY_ATTR
return on_error("Unexpected end of source");
return on_error(
{scan_error::invalid_literal, "Unexpected end of source"});
}

if (auto [after_space_it, cp, is_space] = impl::is_first_char_space(
detail::make_string_view_from_pointers(begin, end));
cp == detail::invalid_code_point) {
SCN_UNLIKELY_ATTR
return on_error("Invalid encoding in format string");
return on_error({scan_error::invalid_format_string,
"Invalid encoding in format string"});
}
else if (is_space) {
// Skip all whitespace in input
Expand All @@ -1969,7 +1977,8 @@ struct format_handler : format_handler_base {

if (*it != *begin) {
SCN_UNLIKELY_ATTR
return on_error("Unexpected literal character in source");
return on_error({scan_error::invalid_literal,
"Unexpected literal character in source"});
}
get_ctx().advance_to(ranges::next(it));
}
Expand Down Expand Up @@ -2037,7 +2046,8 @@ struct format_handler : format_handler_base {
begin = detail::parse_format_specs(begin, end, handler);
if (begin == end || *begin != char_type{'}'}) {
SCN_UNLIKELY_ATTR
on_error("Missing '}' in format string");
on_error({scan_error::invalid_format_string,
"Missing '}' in format string"});
return parse_ctx.begin();
}
if (SCN_UNLIKELY(!handler.get_error())) {
Expand Down
36 changes: 17 additions & 19 deletions src/scn/impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4594,19 +4594,19 @@ struct regex_matches_reader
{
if constexpr (!std::is_same_v<SourceCharT, DestCharT>) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
scan_error::invalid_format_string,
"Cannot transcode is regex_matches_reader");
}
else if constexpr (!SCN_REGEX_SUPPORTS_WIDE_STRINGS &&
!std::is_same_v<SourceCharT, char>) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
scan_error::invalid_format_string,
"Regex backend doesn't support wide strings as input");
}
else {
if (!is_entire_source_contiguous(range)) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
scan_error::invalid_format_string,
"Cannot use regex with a non-contiguous source "
"range");
}
Expand Down Expand Up @@ -4692,12 +4692,12 @@ auto read_string_view_impl(Range range,

if (src.stores_allocated_string()) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
scan_error::invalid_format_string,
"Cannot read a string_view from this source range (not "
"contiguous)");
}
if constexpr (!std::is_same_v<typename src_type::char_type, ValueCharT>) {
return unexpected_scan_error(scan_error::invalid_scanned_value,
return unexpected_scan_error(scan_error::invalid_format_string,
"Cannot read a string_view from "
"this source range (would require "
"transcoding)");
Expand Down Expand Up @@ -4819,13 +4819,13 @@ class regex_string_reader_impl {
if constexpr (!SCN_REGEX_SUPPORTS_WIDE_STRINGS &&
!std::is_same_v<SourceCharT, char>) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
scan_error::invalid_format_string,
"Regex backend doesn't support wide strings as input");
}
else {
if (!is_entire_source_contiguous(range)) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
scan_error::invalid_format_string,
"Cannot use regex with a non-contiguous source "
"range");
}
Expand Down Expand Up @@ -4885,8 +4885,8 @@ class character_reader_impl {
-> scan_expected<ranges::const_iterator_t<Range>>
{
return unexpected_scan_error(
scan_error::invalid_scanned_value,
"character_reader requires take_width_view");
scan_error::invalid_format_string,
"Cannot read characters {:c} without maximum field width");
}
};

Expand Down Expand Up @@ -5382,8 +5382,9 @@ struct bool_reader : public bool_reader_base {
return *r;
}

return unexpected_scan_error(scan_error::invalid_scanned_value,
"read_textual: No match");
return unexpected_scan_error(
scan_error::invalid_scanned_value,
"Failed to read textual boolean: No match");
}
};

Expand Down Expand Up @@ -5910,18 +5911,15 @@ SCN_MAYBE_UNUSED constexpr scan_expected<void> check_widths_for_arg_reader(
if (specs.width != 0) {
if (prefix_width + value_width + postfix_width < specs.width) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
scan_error::length_too_short,
"Scanned value too narrow, width did not exceed what "
"was specified in the format string");
}
}
if (specs.precision != 0) {
if (prefix_width + value_width + postfix_width > specs.precision) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
"Scanned value too wide, width exceeded the specified "
"precision");
}
// Ensured by take_width_view
SCN_ENSURE(prefix_width + value_width + postfix_width <=
specs.precision);
}
return {};
}
Expand Down Expand Up @@ -6049,7 +6047,7 @@ struct arg_reader {
if (specs.precision != 0) {
if (specs.precision <= prefix_width) {
return unexpected_scan_error(
scan_error::invalid_scanned_value,
scan_error::invalid_fill,
"Too many fill characters before value, "
"precision exceeded before reading value");
}
Expand Down
46 changes: 40 additions & 6 deletions tests/unittests/align_and_fill_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ TEST(AlignAndFillTest, P1729_Ex3r11)
{
auto r = scn::scan<int>("42", "{:*>5}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_scanned_value);
EXPECT_EQ(r.error().code(), scn::scan_error::length_too_short);
}
TEST(AlignAndFillTest, P1729_Ex3r12)
{
Expand All @@ -343,7 +343,7 @@ TEST(AlignAndFillTest, P1729_Ex3r13)
{
auto r = scn::scan<int>("42", "{:*>5.5}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_scanned_value);
EXPECT_EQ(r.error().code(), scn::scan_error::length_too_short);
}

TEST(AlignAndFillTest, P1729_Ex3r14)
Expand Down Expand Up @@ -393,7 +393,7 @@ TEST(AlignAndFillTest, P1729_Ex3r20)
{
auto r = scn::scan<int>("42", "{:*<5}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_scanned_value);
EXPECT_EQ(r.error().code(), scn::scan_error::length_too_short);
}
TEST(AlignAndFillTest, P1729_Ex3r21)
{
Expand All @@ -406,7 +406,7 @@ TEST(AlignAndFillTest, P1729_Ex3r22)
{
auto r = scn::scan<int>("42", "{:*<5.5}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_scanned_value);
EXPECT_EQ(r.error().code(), scn::scan_error::length_too_short);
}

TEST(AlignAndFillTest, P1729_Ex3r23)
Expand Down Expand Up @@ -463,7 +463,7 @@ TEST(AlignAndFillTest, P1729_Ex3r30)
{
auto r = scn::scan<int>("**42*", "{:*^6}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_scanned_value);
EXPECT_EQ(r.error().code(), scn::scan_error::length_too_short);
}
TEST(AlignAndFillTest, P1729_Ex3r31)
{
Expand All @@ -476,9 +476,43 @@ TEST(AlignAndFillTest, P1729_Ex3r32)
{
auto r = scn::scan<int>("**42*", "{:*^6.6}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::length_too_short);
}

TEST(AlignAndFillTest, P1729_Ex3r33)
{
auto r = scn::scan<int>("#*42*", "{:*^}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_scanned_value);
}
TEST(AlignAndFillTest, P1729_Ex3r34)
{
auto r = scn::scan<int>("#*42*", "#{:*^}");
ASSERT_TRUE(r);
EXPECT_EQ(r->value(), 42);
EXPECT_STREQ(r->begin(), "");
}
TEST(AlignAndFillTest, P1729_Ex3r35)
{
auto r = scn::scan<int>("#*42*", "#{:#^}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_scanned_value);
}

TEST(AlignAndFillTest, P1729_Ex3r36)
{
auto r = scn::scan<int>("***42*", "{:*^3}");
ASSERT_TRUE(r);
EXPECT_EQ(r->value(), 42);
EXPECT_STREQ(r->begin(), "");
}
TEST(AlignAndFillTest, P1729_Ex3r37)
{
auto r = scn::scan<int>("***42*", "{:*^.3}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_fill);
}

TEST(AlignAndFillTest, PythonParse1)
{
auto r = scn::scan<std::string>("with a herring", "with {:>} herring");
Expand Down Expand Up @@ -529,7 +563,7 @@ TEST(AlignAndFillTest, PythonParse4)
{
auto r = scn::scan<std::string, std::string>("look at that", "{:4}{:4}");
ASSERT_FALSE(r);
EXPECT_EQ(r.error().code(), scn::scan_error::invalid_scanned_value);
EXPECT_EQ(r.error().code(), scn::scan_error::length_too_short);
}
TEST(AlignAndFillTest, PythonParse5)
{
Expand Down

0 comments on commit 65beffa

Please sign in to comment.