Skip to content

Commit 6899b33

Browse files
committed
Discard formatting options for user-defined types during compile time
Parsing will occur at runtime, so for now, just allow any options to appear at compile time. The caveat is that nested replacement fields are currently forbidden. The format string parser will need to handle these at compile time still because the user-defined formatter will not have access to the parameter type information that was inferred at compile time.
1 parent 6f5248b commit 6899b33

File tree

2 files changed

+79
-70
lines changed

2 files changed

+79
-70
lines changed

fly/types/string/detail/format_string.hpp

+40-6
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ class BasicFormatString
6868

6969
/**
7070
* Parse a replacement field for a user-defined type.
71+
*
72+
* @param specifier The replacement field being parsed.
7173
*/
72-
constexpr void parse_user_defined_specifier();
74+
constexpr void parse_user_defined_specifier(FormatSpecifier &specifier);
7375

7476
/**
7577
* Parse a replacement field for a standard type.
@@ -163,7 +165,7 @@ constexpr auto BasicFormatString<CharType, ParameterTypes...>::parse_specifier()
163165

164166
if (m_context.parameter_type(specifier.m_position) == ParameterType::UserDefined)
165167
{
166-
parse_user_defined_specifier();
168+
parse_user_defined_specifier(specifier);
167169
}
168170
else
169171
{
@@ -176,12 +178,44 @@ constexpr auto BasicFormatString<CharType, ParameterTypes...>::parse_specifier()
176178

177179
//==================================================================================================
178180
template <typename CharType, typename... ParameterTypes>
179-
constexpr void BasicFormatString<CharType, ParameterTypes...>::parse_user_defined_specifier()
181+
constexpr void BasicFormatString<CharType, ParameterTypes...>::parse_user_defined_specifier(
182+
FormatSpecifier &specifier)
180183
{
181-
// TODO: For now, user-defined format parameters must be formatted with "{}".
182-
if (!m_context.lexer().consume_if(s_right_brace))
184+
// Replacement fields for user-defined types are parsed at runtime.
185+
//
186+
// TODO: Formatters that inherit from standard formatters might be parsed at compile time.
187+
// TODO: Allow nested format specifiers.
188+
std::size_t expected_close_brace_count = 1;
189+
std::size_t nested_specifier_count = 0;
190+
191+
while (expected_close_brace_count != 0)
183192
{
184-
m_context.on_error("Detected unclosed replacement field - must end with }");
193+
if (auto ch = m_context.lexer().consume(); ch)
194+
{
195+
if (ch == s_right_brace)
196+
{
197+
--expected_close_brace_count;
198+
}
199+
else if (ch == s_left_brace)
200+
{
201+
++expected_close_brace_count;
202+
++nested_specifier_count;
203+
}
204+
else if (ch == s_colon)
205+
{
206+
specifier.m_parse_index = m_context.lexer().position();
207+
}
208+
}
209+
else
210+
{
211+
m_context.on_error("Detected unclosed replacement field - must end with }");
212+
expected_close_brace_count = 0;
213+
}
214+
}
215+
216+
if (nested_specifier_count != 0)
217+
{
218+
m_context.on_error("Nested replacement fields are not allowed in user-defined formatters");
185219
}
186220
}
187221

test/types/string/format_string.cpp

+39-64
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ constexpr const char *s_bad_precision_position =
134134
"Position of precision parameter must be an integral type";
135135
constexpr const char *s_bad_locale =
136136
"Locale-specific form may only be used for numeric and boolean types";
137-
constexpr const char *s_bad_user_defined = s_unclosed_string;
137+
constexpr const char *s_bad_user_defined =
138+
"Nested replacement fields are not allowed in user-defined formatters";
138139
constexpr const char *s_bad_character = "Character types must be formatted with {} or {:cbBodxX}";
139140
constexpr const char *s_bad_string = "String types must be formatted with {} or {:s}";
140141
constexpr const char *s_bad_pointer = "Pointer types must be formatted with {} or {:p}";
@@ -461,13 +462,6 @@ CATCH_TEMPLATE_TEST_CASE(
461462
test_format(make_format(FMT("{}"), b), specifier);
462463
}
463464

464-
CATCH_SECTION("User-defined types may be formatted without presentation type")
465-
{
466-
Specifier specifier {};
467-
test_format(make_format(FMT("{}"), g), specifier);
468-
test_format(make_format(FMT("{}"), u), specifier);
469-
}
470-
471465
CATCH_SECTION("Presentation type may be set (character)")
472466
{
473467
Specifier specifier {};
@@ -648,9 +642,17 @@ CATCH_TEMPLATE_TEST_CASE(
648642
test_format(make_format(FMT("{1:F}"), f, f), specifier);
649643
}
650644

645+
CATCH_SECTION("User-defined types may have any formatting options")
646+
{
647+
Specifier specifier {};
648+
test_format(make_format(FMT("{}"), g), specifier);
649+
test_format(make_format(FMT("{:g}"), g), specifier);
650+
test_format(make_format(FMT("{:abcdefghijklmnop}"), g), specifier);
651+
}
652+
651653
CATCH_SECTION("Specifiers track their size in the format string")
652654
{
653-
auto format = make_format(FMT("ab {0} cd {1:d} ef {2:#0x}"), 1, 2, 3);
655+
auto format = make_format(FMT("ab {0} cd {1:d} ef {2:abcdefghijklmnop}"), 1, 2, u);
654656
CATCH_CHECK_FALSE(format.context().has_error());
655657

656658
auto specifier1 = format.next_specifier();
@@ -663,15 +665,15 @@ CATCH_TEMPLATE_TEST_CASE(
663665

664666
auto specifier3 = format.next_specifier();
665667
CATCH_REQUIRE(specifier3);
666-
CATCH_CHECK(specifier3->m_size == 7);
668+
CATCH_CHECK(specifier3->m_size == 20);
667669

668670
CATCH_CHECK_FALSE(format.next_specifier());
669671
}
670672

671673
CATCH_SECTION("Specifiers track their parsing index in the format string")
672674
{
673675
{
674-
auto format = make_format(FMT("ab {0} cd {1:d} ef {002:#0x}"), 1, 2, 3);
676+
auto format = make_format(FMT("ab {0} cd {1:d} ef {002:abcdefghijklmnop}"), 1, 2, u);
675677
CATCH_CHECK_FALSE(format.context().has_error());
676678

677679
auto specifier1 = format.next_specifier();
@@ -689,7 +691,7 @@ CATCH_TEMPLATE_TEST_CASE(
689691
CATCH_CHECK_FALSE(format.next_specifier());
690692
}
691693
{
692-
auto format = make_format(FMT("ab {} cd {:d} ef {:#0x}"), 1, 2, 3);
694+
auto format = make_format(FMT("ab {} cd {:abcdefghijklmnop} ef {:d}"), 1, u, 2);
693695
CATCH_CHECK_FALSE(format.context().has_error());
694696

695697
auto specifier1 = format.next_specifier();
@@ -702,7 +704,7 @@ CATCH_TEMPLATE_TEST_CASE(
702704

703705
auto specifier3 = format.next_specifier();
704706
CATCH_REQUIRE(specifier3);
705-
CATCH_CHECK(specifier3->m_parse_index == 19);
707+
CATCH_CHECK(specifier3->m_parse_index == 34);
706708

707709
CATCH_CHECK_FALSE(format.next_specifier());
708710
}
@@ -729,12 +731,13 @@ CATCH_TEMPLATE_TEST_CASE(
729731
constexpr const int i = 1;
730732
constexpr const float f = 3.14f;
731733
constexpr const bool b = true;
732-
constexpr const UserFormattedEnum u = UserFormattedEnum::One;
733734

734735
CATCH_SECTION("Cannot parse single opening brace")
735736
{
736737
test_error(make_format(FMT("{"), 1), s_unclosed_string);
737738
test_error(make_format(FMT("{:"), 1), s_unclosed_string);
739+
test_error(make_format(FMT("{"), g), s_unclosed_string);
740+
test_error(make_format(FMT("{:"), g), s_unclosed_string);
738741
}
739742

740743
CATCH_SECTION("Cannot parse single closing brace")
@@ -824,7 +827,6 @@ CATCH_TEMPLATE_TEST_CASE(
824827
{
825828
test_error(make_format(FMT("{:#d}"), i), s_bad_alternate_form);
826829
test_error(make_format(FMT("{:#}"), s), s_bad_alternate_form);
827-
test_error(make_format(FMT("{:#}"), g), s_bad_user_defined);
828830
}
829831

830832
CATCH_SECTION("Zero-padding only valid for numeric types")
@@ -920,26 +922,21 @@ CATCH_TEMPLATE_TEST_CASE(
920922

921923
CATCH_SECTION("Precision type mismatch (character)")
922924
{
923-
test_error(make_format(FMT("{:c}"), g), s_bad_user_defined);
924-
test_error(make_format(FMT("{:c}"), u), s_bad_user_defined);
925925
test_error(make_format(FMT("{:c}"), s), s_bad_string);
926-
test_error(make_format(FMT("{:c}"), &g), s_bad_pointer);
926+
test_error(make_format(FMT("{:c}"), &i), s_bad_pointer);
927927
test_error(make_format(FMT("{:c}"), f), s_bad_float);
928928
}
929929

930930
CATCH_SECTION("Precision type mismatch (string)")
931931
{
932-
test_error(make_format(FMT("{:c}"), g), s_bad_user_defined);
933-
test_error(make_format(FMT("{:c}"), u), s_bad_user_defined);
934932
test_error(make_format(FMT("{:s}"), c), s_bad_character);
935-
test_error(make_format(FMT("{:s}"), &g), s_bad_pointer);
933+
test_error(make_format(FMT("{:s}"), &i), s_bad_pointer);
936934
test_error(make_format(FMT("{:s}"), i), s_bad_integer);
937935
test_error(make_format(FMT("{:s}"), f), s_bad_float);
938936
}
939937

940938
CATCH_SECTION("Precision type mismatch (pointer)")
941939
{
942-
test_error(make_format(FMT("{:p}"), g), s_bad_user_defined);
943940
test_error(make_format(FMT("{:p}"), c), s_bad_character);
944941
test_error(make_format(FMT("{:p}"), i), s_bad_integer);
945942
test_error(make_format(FMT("{:p}"), f), s_bad_float);
@@ -948,123 +945,96 @@ CATCH_TEMPLATE_TEST_CASE(
948945

949946
CATCH_SECTION("Precision type mismatch (binary)")
950947
{
951-
test_error(make_format(FMT("{:b}"), g), s_bad_user_defined);
952-
test_error(make_format(FMT("{:b}"), u), s_bad_user_defined);
953948
test_error(make_format(FMT("{:b}"), s), s_bad_string);
954-
test_error(make_format(FMT("{:b}"), &g), s_bad_pointer);
949+
test_error(make_format(FMT("{:b}"), &i), s_bad_pointer);
955950
test_error(make_format(FMT("{:b}"), f), s_bad_float);
956951

957-
test_error(make_format(FMT("{:B}"), g), s_bad_user_defined);
958952
test_error(make_format(FMT("{:B}"), s), s_bad_string);
959-
test_error(make_format(FMT("{:B}"), &g), s_bad_pointer);
953+
test_error(make_format(FMT("{:B}"), &i), s_bad_pointer);
960954
test_error(make_format(FMT("{:B}"), f), s_bad_float);
961955
}
962956

963957
CATCH_SECTION("Precision type mismatch (octal)")
964958
{
965-
test_error(make_format(FMT("{:o}"), g), s_bad_user_defined);
966-
test_error(make_format(FMT("{:o}"), u), s_bad_user_defined);
967959
test_error(make_format(FMT("{:o}"), s), s_bad_string);
968-
test_error(make_format(FMT("{:o}"), &g), s_bad_pointer);
960+
test_error(make_format(FMT("{:o}"), &i), s_bad_pointer);
969961
test_error(make_format(FMT("{:o}"), f), s_bad_float);
970962
}
971963

972964
CATCH_SECTION("Precision type mismatch (decimal)")
973965
{
974-
test_error(make_format(FMT("{:d}"), g), s_bad_user_defined);
975-
test_error(make_format(FMT("{:d}"), u), s_bad_user_defined);
976966
test_error(make_format(FMT("{:d}"), s), s_bad_string);
977-
test_error(make_format(FMT("{:d}"), &g), s_bad_pointer);
967+
test_error(make_format(FMT("{:d}"), &i), s_bad_pointer);
978968
test_error(make_format(FMT("{:d}"), f), s_bad_float);
979969
}
980970

981971
CATCH_SECTION("Precision type mismatch (hex)")
982972
{
983-
test_error(make_format(FMT("{:x}"), g), s_bad_user_defined);
984-
test_error(make_format(FMT("{:x}"), u), s_bad_user_defined);
985973
test_error(make_format(FMT("{:x}"), s), s_bad_string);
986-
test_error(make_format(FMT("{:x}"), &g), s_bad_pointer);
974+
test_error(make_format(FMT("{:x}"), &i), s_bad_pointer);
987975
test_error(make_format(FMT("{:x}"), f), s_bad_float);
988976

989-
test_error(make_format(FMT("{:X}"), g), s_bad_user_defined);
990-
test_error(make_format(FMT("{:X}"), u), s_bad_user_defined);
991977
test_error(make_format(FMT("{:X}"), s), s_bad_string);
992-
test_error(make_format(FMT("{:X}"), &g), s_bad_pointer);
978+
test_error(make_format(FMT("{:X}"), &i), s_bad_pointer);
993979
test_error(make_format(FMT("{:X}"), f), s_bad_float);
994980
}
995981

996982
CATCH_SECTION("Precision type mismatch (hexfloat)")
997983
{
998-
test_error(make_format(FMT("{:a}"), g), s_bad_user_defined);
999-
test_error(make_format(FMT("{:a}"), u), s_bad_user_defined);
1000984
test_error(make_format(FMT("{:a}"), c), s_bad_character);
1001985
test_error(make_format(FMT("{:a}"), s), s_bad_string);
1002-
test_error(make_format(FMT("{:a}"), &g), s_bad_pointer);
986+
test_error(make_format(FMT("{:a}"), &i), s_bad_pointer);
1003987
test_error(make_format(FMT("{:a}"), i), s_bad_integer);
1004988
test_error(make_format(FMT("{:a}"), b), s_bad_bool);
1005989

1006-
test_error(make_format(FMT("{:A}"), g), s_bad_user_defined);
1007-
test_error(make_format(FMT("{:A}"), u), s_bad_user_defined);
1008990
test_error(make_format(FMT("{:A}"), c), s_bad_character);
1009991
test_error(make_format(FMT("{:A}"), s), s_bad_string);
1010-
test_error(make_format(FMT("{:A}"), &g), s_bad_pointer);
992+
test_error(make_format(FMT("{:A}"), &i), s_bad_pointer);
1011993
test_error(make_format(FMT("{:A}"), i), s_bad_integer);
1012994
test_error(make_format(FMT("{:A}"), b), s_bad_bool);
1013995
}
1014996

1015997
CATCH_SECTION("Precision type mismatch (scientific)")
1016998
{
1017-
test_error(make_format(FMT("{:e}"), g), s_bad_user_defined);
1018-
test_error(make_format(FMT("{:e}"), u), s_bad_user_defined);
1019999
test_error(make_format(FMT("{:e}"), c), s_bad_character);
10201000
test_error(make_format(FMT("{:e}"), s), s_bad_string);
1021-
test_error(make_format(FMT("{:e}"), &g), s_bad_pointer);
1001+
test_error(make_format(FMT("{:e}"), &i), s_bad_pointer);
10221002
test_error(make_format(FMT("{:e}"), i), s_bad_integer);
10231003
test_error(make_format(FMT("{:e}"), b), s_bad_bool);
10241004

1025-
test_error(make_format(FMT("{:E}"), g), s_bad_user_defined);
1026-
test_error(make_format(FMT("{:E}"), u), s_bad_user_defined);
10271005
test_error(make_format(FMT("{:E}"), c), s_bad_character);
10281006
test_error(make_format(FMT("{:E}"), s), s_bad_string);
1029-
test_error(make_format(FMT("{:E}"), &g), s_bad_pointer);
1007+
test_error(make_format(FMT("{:E}"), &i), s_bad_pointer);
10301008
test_error(make_format(FMT("{:E}"), i), s_bad_integer);
10311009
test_error(make_format(FMT("{:E}"), b), s_bad_bool);
10321010
}
10331011

10341012
CATCH_SECTION("Precision type mismatch (fixed)")
10351013
{
1036-
test_error(make_format(FMT("{:f}"), g), s_bad_user_defined);
1037-
test_error(make_format(FMT("{:f}"), u), s_bad_user_defined);
10381014
test_error(make_format(FMT("{:f}"), c), s_bad_character);
10391015
test_error(make_format(FMT("{:f}"), s), s_bad_string);
1040-
test_error(make_format(FMT("{:f}"), &g), s_bad_pointer);
1016+
test_error(make_format(FMT("{:f}"), &i), s_bad_pointer);
10411017
test_error(make_format(FMT("{:f}"), i), s_bad_integer);
10421018
test_error(make_format(FMT("{:f}"), b), s_bad_bool);
10431019

1044-
test_error(make_format(FMT("{:F}"), g), s_bad_user_defined);
1045-
test_error(make_format(FMT("{:F}"), u), s_bad_user_defined);
10461020
test_error(make_format(FMT("{:F}"), c), s_bad_character);
10471021
test_error(make_format(FMT("{:F}"), s), s_bad_string);
1048-
test_error(make_format(FMT("{:F}"), &g), s_bad_pointer);
1022+
test_error(make_format(FMT("{:F}"), &i), s_bad_pointer);
10491023
test_error(make_format(FMT("{:F}"), i), s_bad_integer);
10501024
test_error(make_format(FMT("{:F}"), b), s_bad_bool);
10511025
}
10521026

10531027
CATCH_SECTION("Precision type mismatch (general)")
10541028
{
1055-
test_error(make_format(FMT("{:g}"), g), s_bad_user_defined);
1056-
test_error(make_format(FMT("{:g}"), u), s_bad_user_defined);
10571029
test_error(make_format(FMT("{:g}"), c), s_bad_character);
10581030
test_error(make_format(FMT("{:g}"), s), s_bad_string);
1059-
test_error(make_format(FMT("{:g}"), &g), s_bad_pointer);
1031+
test_error(make_format(FMT("{:g}"), &i), s_bad_pointer);
10601032
test_error(make_format(FMT("{:g}"), i), s_bad_integer);
10611033
test_error(make_format(FMT("{:g}"), b), s_bad_bool);
10621034

1063-
test_error(make_format(FMT("{:G}"), g), s_bad_user_defined);
1064-
test_error(make_format(FMT("{:G}"), u), s_bad_user_defined);
10651035
test_error(make_format(FMT("{:G}"), c), s_bad_character);
10661036
test_error(make_format(FMT("{:G}"), s), s_bad_string);
1067-
test_error(make_format(FMT("{:G}"), &g), s_bad_pointer);
1037+
test_error(make_format(FMT("{:G}"), &i), s_bad_pointer);
10681038
test_error(make_format(FMT("{:G}"), i), s_bad_integer);
10691039
test_error(make_format(FMT("{:G}"), b), s_bad_bool);
10701040
}
@@ -1073,7 +1043,7 @@ CATCH_TEMPLATE_TEST_CASE(
10731043
{
10741044
test_error(make_format(FMT("{:cs}"), c), s_unclosed_string);
10751045
test_error(make_format(FMT("{:ss}"), s), s_unclosed_string);
1076-
test_error(make_format(FMT("{:ps}"), &g), s_unclosed_string);
1046+
test_error(make_format(FMT("{:ps}"), &i), s_unclosed_string);
10771047
test_error(make_format(FMT("{:bs}"), i), s_unclosed_string);
10781048
test_error(make_format(FMT("{:Bs}"), i), s_unclosed_string);
10791049
test_error(make_format(FMT("{:os}"), i), s_unclosed_string);
@@ -1130,6 +1100,11 @@ CATCH_TEMPLATE_TEST_CASE(
11301100
test_error(make_format(FMT("{:Z}"), i), s_unclosed_string);
11311101
}
11321102

1103+
CATCH_SECTION("User-defined types may not have nested replacement fields")
1104+
{
1105+
test_error(make_format(FMT("{:{}}"), g), s_bad_user_defined);
1106+
}
1107+
11331108
CATCH_SECTION("Cannot parse erroneous whitespace")
11341109
{
11351110
test_error(make_format(FMT("{ 0:_^+#01.2Lf}"), f), s_unclosed_string);

0 commit comments

Comments
 (0)