Skip to content

Commit

Permalink
allow lowercase 't' and 'z' in datetimes (per spec)
Browse files Browse the repository at this point in the history
also:
- updated conformance tests
  • Loading branch information
marzer committed Jul 4, 2021
1 parent ba75446 commit 4f21332
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 51 deletions.
2 changes: 1 addition & 1 deletion external/toml-test
37 changes: 18 additions & 19 deletions include/toml++/toml_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1741,7 +1741,7 @@ TOML_IMPL_NAMESPACE_START
{
if (is_eof()
|| is_value_terminator(*cp)
|| (part_of_datetime && is_match(*cp, U'+', U'-', U'Z')))
|| (part_of_datetime && is_match(*cp, U'+', U'-', U'Z', U'z')))
return time;
}
else
Expand All @@ -1764,7 +1764,7 @@ TOML_IMPL_NAMESPACE_START
// '.' (early-exiting is allowed; fractional is optional)
if (is_eof()
|| is_value_terminator(*cp)
|| (part_of_datetime && is_match(*cp, U'+', U'-', U'Z')))
|| (part_of_datetime && is_match(*cp, U'+', U'-', U'Z', U'z')))
return time;
if (*cp != U'.')
set_error_and_return_default("expected '.', saw '"sv, to_sv(*cp), "'"sv);
Expand Down Expand Up @@ -1812,9 +1812,9 @@ TOML_IMPL_NAMESPACE_START
auto date = parse_date(true);
set_error_and_return_if_eof({});

// ' ' or 'T'
if (!is_match(*cp, U' ', U'T'))
set_error_and_return_default("expected space or 'T', saw '"sv, to_sv(*cp), "'"sv);
// ' ', 'T' or 't'
if (!is_match(*cp, U' ', U'T', U't'))
set_error_and_return_default("expected space, 'T' or 't', saw '"sv, to_sv(*cp), "'"sv);
advance_and_return_if_error_or_eof({});

// "HH:MM:SS.FFFFFFFFF"
Expand All @@ -1825,9 +1825,9 @@ TOML_IMPL_NAMESPACE_START
if (is_eof() || is_value_terminator(*cp))
return { date, time };

// zero offset ("Z")
// zero offset ('Z' or 'z')
time_offset offset;
if (*cp == U'Z')
if (is_match(*cp, U'Z', U'z'))
advance_and_return_if_error({});

// explicit offset ("+/-HH:MM")
Expand Down Expand Up @@ -2161,21 +2161,20 @@ TOML_IMPL_NAMESPACE_START
// (as opposed to the fallback "could not determine type" message)
if (has_any(has_p))
val = new value{ parse_hex_float() };
else if (has_any(has_x))
else if (has_any(has_x | has_o | has_b))
{
val = new value{ parse_integer<16>() };
int64_t i;
if (has_any(has_x))
i = parse_integer<16>();
else if (has_any(has_o))
i = parse_integer<8>();
else // has_b
i = parse_integer<2>();
return_if_error({});

val = new value{ i };
reinterpret_cast<value<int64_t>*>(val.get())->flags(value_flags::format_as_hexadecimal);
}
else if (has_any(has_o))
{
val = new value{ parse_integer<8>() };
reinterpret_cast<value<int64_t>*>(val.get())->flags(value_flags::format_as_octal);
}
else if (has_any(has_b))
{
val = new value{ parse_integer<2>() };
reinterpret_cast<value<int64_t>*>(val.get())->flags(value_flags::format_as_binary);
}
else if (has_any(has_e) || (has_any(begins_zero | begins_digit) && chars[1] == U'.'))
val = new value{ parse_float() };
else if (has_any(begins_sign))
Expand Down
82 changes: 72 additions & 10 deletions tests/conformance_burntsushi_valid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@ namespace
static constexpr auto array_bool = R"(a = [true, false])"sv;
static constexpr auto array_empty = R"(thevoid = [[[[[]]]]])"sv;
static constexpr auto array_hetergeneous = R"(mixed = [[1, 2], ["a", "b"], [1.1, 2.1]])"sv;
static constexpr auto array_mix_string_table = R"(numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]
contributors = [
static constexpr auto array_mixed_int_array = R"(arrays-and-ints = [1, ["Arrays are not integers."]])"sv;
static constexpr auto array_mixed_int_float = R"(ints-and-floats = [1, 1.1])"sv;
static constexpr auto array_mixed_int_string = R"(strings-and-ints = ["hi", 42])"sv;
static constexpr auto array_mixed_string_table = R"(contributors = [
"Foo Bar <[email protected]>",
{ name = "Baz Qux", email = "[email protected]", url = "https://example.com/bazqux" }
])"sv;
static constexpr auto array_nested_double = R"(nest = [
[
["a"],
[1, 2, [3]]
]
])"sv;
static constexpr auto array_nested_inline_table = R"(a = [ { b = {} } ])"sv;
static constexpr auto array_nested = R"(nest = [["a"], ["b"]])"sv;
Expand Down Expand Up @@ -585,7 +593,48 @@ TEST_CASE("conformance - burntsushi/valid")
REQUIRE(tbl == expected);
});

parsing_should_succeed(FILE_LINE_ARGS, array_mix_string_table, [](toml::table&& tbl)
parsing_should_succeed(FILE_LINE_ARGS, array_mixed_int_array, [](toml::table&& tbl)
{
const auto expected = toml::table{{
{
R"(arrays-and-ints)"sv, toml::array{
1,
toml::array{
R"(Arrays are not integers.)"sv,
},
}
},
}};
REQUIRE(tbl == expected);
});

parsing_should_succeed(FILE_LINE_ARGS, array_mixed_int_float, [](toml::table&& tbl)
{
const auto expected = toml::table{{
{
R"(ints-and-floats)"sv, toml::array{
1,
1.1,
}
},
}};
REQUIRE(tbl == expected);
});

parsing_should_succeed(FILE_LINE_ARGS, array_mixed_int_string, [](toml::table&& tbl)
{
const auto expected = toml::table{{
{
R"(strings-and-ints)"sv, toml::array{
R"(hi)"sv,
42,
}
},
}};
REQUIRE(tbl == expected);
});

parsing_should_succeed(FILE_LINE_ARGS, array_mixed_string_table, [](toml::table&& tbl)
{
const auto expected = toml::table{{
{
Expand All @@ -598,14 +647,27 @@ TEST_CASE("conformance - burntsushi/valid")
}},
}
},
}};
REQUIRE(tbl == expected);
});

parsing_should_succeed(FILE_LINE_ARGS, array_nested_double, [](toml::table&& tbl)
{
const auto expected = toml::table{{
{
R"(numbers)"sv, toml::array{
0.1,
0.2,
0.5,
1,
2,
5,
R"(nest)"sv, toml::array{
toml::inserter{toml::array{
toml::array{
R"(a)"sv,
},
toml::array{
1,
2,
toml::array{
3,
},
},
}},
}
},
}};
Expand Down
14 changes: 14 additions & 0 deletions tests/parsing_dates_and_times.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,42 +52,56 @@ TEST_CASE("parsing - dates and times")
{
const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30 } };
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30"sv, val);
}
{
const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30 }, { -9, -30 } };
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30-09:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30-09:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30-09:30"sv, val);
}
{
const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30 }, { 9, 30 } };
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30+09:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30+09:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30+09:30"sv, val);
}
{
const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30, 40000000 } };
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30.04"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04"sv, val);
}
{
const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30, 40000000 }, { -9, -30 } };
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04-09:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30.04-09:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04-09:30"sv, val);
}
{
const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30, 40000000 }, { 9, 30 } };
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04+09:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30.04+09:30"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04+09:30"sv, val);
}
{
const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30 }, {} };
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30Z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30Z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30Z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30z"sv, val);
}
{
const auto val = date_time{ { 1987, 3, 16 }, { 10, 20, 30, 40000000 }, {} };
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04Z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30.04Z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04Z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16T10:20:30.04z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16t10:20:30.04z"sv, val);
parse_expected_value(FILE_LINE_ARGS, "1987-03-16 10:20:30.04z"sv, val);
}

// toml/issues/671 (allow omission of seconds)
Expand Down
37 changes: 18 additions & 19 deletions toml.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10434,7 +10434,7 @@ TOML_IMPL_NAMESPACE_START
{
if (is_eof()
|| is_value_terminator(*cp)
|| (part_of_datetime && is_match(*cp, U'+', U'-', U'Z')))
|| (part_of_datetime && is_match(*cp, U'+', U'-', U'Z', U'z')))
return time;
}
else
Expand All @@ -10456,7 +10456,7 @@ TOML_IMPL_NAMESPACE_START
// '.' (early-exiting is allowed; fractional is optional)
if (is_eof()
|| is_value_terminator(*cp)
|| (part_of_datetime && is_match(*cp, U'+', U'-', U'Z')))
|| (part_of_datetime && is_match(*cp, U'+', U'-', U'Z', U'z')))
return time;
if (*cp != U'.')
set_error_and_return_default("expected '.', saw '"sv, to_sv(*cp), "'"sv);
Expand Down Expand Up @@ -10504,9 +10504,9 @@ TOML_IMPL_NAMESPACE_START
auto date = parse_date(true);
set_error_and_return_if_eof({});

// ' ' or 'T'
if (!is_match(*cp, U' ', U'T'))
set_error_and_return_default("expected space or 'T', saw '"sv, to_sv(*cp), "'"sv);
// ' ', 'T' or 't'
if (!is_match(*cp, U' ', U'T', U't'))
set_error_and_return_default("expected space, 'T' or 't', saw '"sv, to_sv(*cp), "'"sv);
advance_and_return_if_error_or_eof({});

// "HH:MM:SS.FFFFFFFFF"
Expand All @@ -10517,9 +10517,9 @@ TOML_IMPL_NAMESPACE_START
if (is_eof() || is_value_terminator(*cp))
return { date, time };

// zero offset ("Z")
// zero offset ('Z' or 'z')
time_offset offset;
if (*cp == U'Z')
if (is_match(*cp, U'Z', U'z'))
advance_and_return_if_error({});

// explicit offset ("+/-HH:MM")
Expand Down Expand Up @@ -10852,21 +10852,20 @@ TOML_IMPL_NAMESPACE_START
// (as opposed to the fallback "could not determine type" message)
if (has_any(has_p))
val = new value{ parse_hex_float() };
else if (has_any(has_x))
else if (has_any(has_x | has_o | has_b))
{
val = new value{ parse_integer<16>() };
int64_t i;
if (has_any(has_x))
i = parse_integer<16>();
else if (has_any(has_o))
i = parse_integer<8>();
else // has_b
i = parse_integer<2>();
return_if_error({});

val = new value{ i };
reinterpret_cast<value<int64_t>*>(val.get())->flags(value_flags::format_as_hexadecimal);
}
else if (has_any(has_o))
{
val = new value{ parse_integer<8>() };
reinterpret_cast<value<int64_t>*>(val.get())->flags(value_flags::format_as_octal);
}
else if (has_any(has_b))
{
val = new value{ parse_integer<2>() };
reinterpret_cast<value<int64_t>*>(val.get())->flags(value_flags::format_as_binary);
}
else if (has_any(has_e) || (has_any(begins_zero | begins_digit) && chars[1] == U'.'))
val = new value{ parse_float() };
else if (has_any(begins_sign))
Expand Down
2 changes: 0 additions & 2 deletions tools/generate_conformance_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,6 @@ def load_valid_inputs(tests, extern_root):

def load_invalid_inputs(tests, extern_root):
tests['invalid']['burntsushi'] = load_tests(Path(extern_root, 'toml-test', 'tests', 'invalid'), False, (
# false negatives after TOML 0.4.0
re.compile('array-mixed.*'),
# these break IO/git/visual studio (i test them elsewhere)
re.compile('.*(bom|control).*'),
'encoding-utf16',
Expand Down

0 comments on commit 4f21332

Please sign in to comment.