diff options
author | Abseil Team <absl-team@google.com> | 2018-12-04 11:01:12 -0800 |
---|---|---|
committer | Ashley Hedberg <ahedberg@google.com> | 2018-12-04 16:54:40 -0500 |
commit | fcb104594b0bb4b8ac306cb2f55ecdad40974683 (patch) | |
tree | d2d79d246c6a894ca6716f47c15ebb7b8796b36a /absl/strings | |
parent | 6c7de165d1c82684359ccb630bb5f83263fa5ebc (diff) |
Creation of LTS branch "lts_2018_12_18"20181200
- 44b0fafc62d9b8f192e8180cbe9c4b806b339d57 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 926bfeb9fff223429c12224b7514243886323e8d Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 13327debebc5c2d1d4991b69fe50450e340e50e4 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 3088e76c597e068479e82508b1770a7ad0c806b6 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- f6ae816808cd913e0e2b3e2af14f328fa1071af0 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- a06c4a1d9093137b7217a5aaba8920d62e835dc0 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 7b46e1d31a6b08b1c6da2a13e7b151a20446fa07 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 070f6e47b33a2909d039e620c873204f78809492 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 7990fd459e9339467814ddb95000c87cb1e4d945 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- f95179062eb65ce40895cc76f1398cce25394369 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- cc8dcd307b76a575d2e3e0958a4fe4c7193c2f68 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- a705aa78dc76fc5c79d501e61dcc077eca68a8a4 Merge pull request #194 from Mizux/windows by Xiaoyi Zhang <zhangxy988@gmail.com>
- a4c3ffff11eec0ee45742f915c255e9f870b7e0f Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 01174578651b73021d9b8c3820f6fea707dacdf0 Merge pull request #201 from ccawley2011/fix-byteswap by Matt Calabrese <38107210+mattcalabrese-google@users.noreply.github.com>
- f86f9413856b65afdd61fea938d684b8ab73115a Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 94c298e2a0ae409e283cab96c954a685bd865a70 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 0884a6a04e4497d11b1b398cc0e422b118bf977a Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- c16d5557cd05119b5b7b1318ef778ebe3195b4a1 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 45221ccc4ed643e4209b0cc5798e97203f108fa8 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 2019e17a520575ab365b2b5134d71068182c70b8 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 5b70a8910b2e6fb0ce5193a41873139a126d2f7f Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- a00bdd176d66ef0b417d9576052a19091fbdf891 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- f340f773edab951656b19b6f1a77c964a78ec4c2 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 445998d7ac4e5d3c50411d377e3b50e960d2d6c2 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- e821380d69a549dc64900693942789d21aa4df5e Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- f21d187b80e3b7f08fb279775ea9c8b48c636030 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 5441bbe1db5d0f2ca24b5b60166367b0966790af Fix code snippet in comment (#174) by Loo Rong Jie <loorongjie@gmail.com>
- 5aae0cffae8ffaacab965756169b34e511b353df Fix CMake build (#173) by Stephan Dollberg <stephan.dollberg@gmail.com>
- 48cd2c3f351ff188bc85684b84a91b6e6d17d896 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- e291c279e458761e77a69b09b129d3d1e81f1e80 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- e01d95528ea2137a4a27a88d1f57c6cb260aafed Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 8ff1374008259719b54a8cb128ef951c02da164c Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 02451914b9ad5320f81f56a89f3eef1f8683227c Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 921fd5cf02ec0d665439a790148d59faa7d4a72c Merge pull request #166 from rongjiecomputer/cmake-test by Gennadiy Civil <gennadiycivil@users.noreply.github.com>
- fb462224c058487763f263b7995d70efd0242c17 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- c075ad321696fa5072e097f0a51e4fe76a6fe13e Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 0f4bc966754ec6cd28d5f03467d56f1efdc598e3 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 6c7e5ffc43decd92f7bdfc510ad8a245a20b6dea Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- d6df769173bf0263489f98874b93034db0e479a2 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 28080f5f050c9530aa9f2b39c60d8217038d64ff Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 9c987f429bba32fb4446280fd3b91e2472d71d4d Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 5e7d459eeca7bc53deab0ee9634601386b53d7c0 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- bed5bd6e185c7e0311f3a1f2dab4c96083dac636 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- fefc83638fb69395d259ed245699310610429064 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- d8cfe9f2a77fbee02c09642491e62a3f3677e0f6 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- ad5c960b2eb914881d1ceba0e996a0a8f3f6ca59 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 86f0fe93ad9d6d033a319476736a3256369c1f75 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- f0f15c2778b0e4959244dd25e63f445a455870f5 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 29ff6d4860070bf8fcbd39c8805d0c32d56628a3 Removed "warning treated as error" flag from MSVC (#153) by vocaviking <vocaviking@users.noreply.github.com>
- 083d04dd4a62ebbf037079b06e49b323c5e1192a Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- bea85b52733022294eef108a2e42d77b616ddca2 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 8f96be6ca60d967bd4b37f93d0a03bcff4145200 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 92e07e5590752d6b8e67f7f2f86c6286561e8cea Merge pull request #152 from clnperez/fix-multi-defines-p... by Derek Mauro <761129+derekmauro@users.noreply.github.com>
- 2125e6444a9de9e41f21ecdc674dd7d8759c149d Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 9acad869d21731f5bc50430a33fe61cc0ffcbb0b Merge pull request #150 from OlafvdSpek/patch-2 by Jonathan Cohen <cohenjon@google.com>
- c2e00d341913bf03b4597ade5b056042e23e8c58 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 9e060686d1c325f34f9806b45fe77bafeed00aee Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 7aa411ceafc1272a28579cca739a97a2fb79055a Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 2c5af55ed34850d8b7dd46177c8ca53fdfda920e Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 44aa275286baf97fc13529aca547a88b180beb08 Merge pull request #143 from rongjiecomputer/kernel by Xiaoyi Zhang <zhangxy988@gmail.com>
- 42f22a28401c952f1fc5942231c7fdac80811bf5 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- b973bc53ef366f0253b85eeed9a79b241884a843 Merge pull request #139 from siepkes/smartos-support by ahedberg <ahedberg@google.com>
- e0def7473e52336f58759e11db4cd9467e5e0356 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- f826f1d489b61b64df1d94afbe5981841a82e5fa Merge pull request #138 from edbaunton/remove-deprecated-... by ahedberg <ahedberg@google.com>
- 7b50a4a94b0c7df68b3a854c850b551aaef0a8b4 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- a5030ca5125b9d557ecfeea8acc8b1a8e49f6d27 Merge pull request #144 from rongjiecomputer/winsock2 by Xiaoyi Zhang <zhangxy988@gmail.com>
- 02687955b7ca8fc02ada9b14bc247deeb108d341 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 8f612ebb152fb7e05643a2bcf78cb89a8c0641ad Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 134496a31d8b324f762de3bee9a002658c984456 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- ba8d6cf07766263723e86736f20a51c1c9c67b19 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- be1e84b988fceabcea4fc9e93f899539f0c81901 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 16ac2ec2e38cdf47f9330a312e319d57da659c10 Merge pull request #134 from rongjiecomputer/cmake by Alex Strelnikov <strel@google.com>
- 7efd8dc0f1075356e9c7caa950afd1ecf854e8b9 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 87a4c07856e7dc69958019d47b2f02ae47746ec0 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
- 4491d606df34c44efda47b6d17b605262f17e182 Export of internal Abseil changes. by Abseil Team <absl-team@google.com>
GitOrigin-RevId: 44b0fafc62d9b8f192e8180cbe9c4b806b339d57
Change-Id: I2c427b5b41b2d34101922048b00f3d9dafcb498d
Diffstat (limited to 'absl/strings')
82 files changed, 7500 insertions, 483 deletions
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 328f52f3..3b85f1b4 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -19,6 +19,7 @@ load( "ABSL_DEFAULT_COPTS", "ABSL_TEST_COPTS", "ABSL_EXCEPTIONS_FLAG", + "ABSL_EXCEPTIONS_FLAG_LINKOPTS", ) package( @@ -69,6 +70,7 @@ cc_library( deps = [ ":internal", "//absl/base", + "//absl/base:bits", "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", @@ -86,7 +88,6 @@ cc_library( "internal/utf8.cc", ], hdrs = [ - "internal/bits.h", "internal/char_map.h", "internal/ostringstream.h", "internal/resize_uninitialized.h", @@ -159,6 +160,18 @@ cc_test( ) cc_test( + name = "ascii_benchmark", + srcs = ["ascii_benchmark.cc"], + copts = ABSL_TEST_COPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":strings", + "@com_github_google_benchmark//:benchmark_main", + ], +) + +cc_test( name = "memutil_benchmark", srcs = [ "internal/memutil.h", @@ -200,7 +213,6 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":internal", - ":strings", "//absl/base:core_headers", "@com_google_googletest//:gtest_main", ], @@ -225,6 +237,7 @@ cc_test( size = "small", srcs = ["string_view_test.cc"], copts = ABSL_TEST_COPTS + ABSL_EXCEPTIONS_FLAG, + linkopts = ABSL_EXCEPTIONS_FLAG_LINKOPTS, visibility = ["//visibility:private"], deps = [ ":strings", @@ -361,7 +374,6 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":strings", - "//absl/memory", "@com_github_google_benchmark//:benchmark_main", ], ) @@ -401,6 +413,7 @@ cc_test( copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ + ":pow10_helper", ":strings", "//absl/base", "//absl/base:core_headers", @@ -409,6 +422,19 @@ cc_test( ) cc_test( + name = "numbers_benchmark", + srcs = ["numbers_benchmark.cc"], + copts = ABSL_TEST_COPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":strings", + "//absl/base", + "@com_github_google_benchmark//:benchmark_main", + ], +) + +cc_test( name = "strip_test", size = "small", srcs = ["strip_test.cc"], @@ -446,6 +472,8 @@ cc_test( srcs = ["charconv_test.cc"], copts = ABSL_TEST_COPTS, deps = [ + ":pow10_helper", + ":str_format", ":strings", "//absl/base", "@com_google_googletest//:gtest_main", @@ -486,9 +514,171 @@ cc_test( srcs = [ "charconv_benchmark.cc", ], + tags = [ + "benchmark", + ], deps = [ ":strings", "//absl/base", "@com_github_google_benchmark//:benchmark_main", ], ) + +cc_library( + name = "str_format", + hdrs = [ + "str_format.h", + ], + copts = ABSL_DEFAULT_COPTS, + deps = [ + ":str_format_internal", + ], +) + +cc_library( + name = "str_format_internal", + srcs = [ + "internal/str_format/arg.cc", + "internal/str_format/bind.cc", + "internal/str_format/extension.cc", + "internal/str_format/float_conversion.cc", + "internal/str_format/output.cc", + "internal/str_format/parser.cc", + ], + hdrs = [ + "internal/str_format/arg.h", + "internal/str_format/bind.h", + "internal/str_format/checker.h", + "internal/str_format/extension.h", + "internal/str_format/float_conversion.h", + "internal/str_format/output.h", + "internal/str_format/parser.h", + ], + copts = ABSL_DEFAULT_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":strings", + "//absl/base:core_headers", + "//absl/container:inlined_vector", + "//absl/meta:type_traits", + "//absl/numeric:int128", + "//absl/types:span", + ], +) + +cc_test( + name = "str_format_test", + srcs = ["str_format_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format", + ":strings", + "//absl/base:core_headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "str_format_extension_test", + srcs = [ + "internal/str_format/extension_test.cc", + ], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format", + ":str_format_internal", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "str_format_arg_test", + srcs = ["internal/str_format/arg_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format", + ":str_format_internal", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "str_format_bind_test", + srcs = ["internal/str_format/bind_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format_internal", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "str_format_checker_test", + srcs = ["internal/str_format/checker_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "str_format_convert_test", + size = "small", + srcs = ["internal/str_format/convert_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format_internal", + "//absl/numeric:int128", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "str_format_output_test", + srcs = ["internal/str_format/output_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format_internal", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "str_format_parser_test", + srcs = ["internal/str_format/parser_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format_internal", + "//absl/base:core_headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "pow10_helper", + testonly = True, + srcs = ["internal/pow10_helper.cc"], + hdrs = ["internal/pow10_helper.h"], + visibility = ["//visibility:private"], +) + +cc_test( + name = "pow10_helper_test", + srcs = ["internal/pow10_helper_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":pow10_helper", + ":str_format", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index cab2c456..5b877ad1 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -32,7 +32,6 @@ list(APPEND STRINGS_PUBLIC_HEADERS list(APPEND STRINGS_INTERNAL_HEADERS - "internal/bits.h" "internal/char_map.h" "internal/charconv_bigint.h" "internal/charconv_parse.h" @@ -68,7 +67,7 @@ list(APPEND STRINGS_SRC ${STRINGS_PUBLIC_HEADERS} ${STRINGS_INTERNAL_HEADERS} ) -set(STRINGS_PUBLIC_LIBRARIES absl::base absl_throw_delegate) +set(STRINGS_PUBLIC_LIBRARIES absl::base absl_internal_throw_delegate) absl_library( TARGET @@ -81,6 +80,65 @@ absl_library( strings ) +# add str_format library +absl_header_library( + TARGET + absl_str_format + PUBLIC_LIBRARIES + str_format_internal + EXPORT_NAME + str_format +) + +# str_format_internal +absl_library( + TARGET + str_format_internal + SOURCES + "internal/str_format/arg.cc" + "internal/str_format/bind.cc" + "internal/str_format/extension.cc" + "internal/str_format/float_conversion.cc" + "internal/str_format/output.cc" + "internal/str_format/parser.cc" + "internal/str_format/arg.h" + "internal/str_format/bind.h" + "internal/str_format/checker.h" + "internal/str_format/extension.h" + "internal/str_format/float_conversion.h" + "internal/str_format/output.h" + "internal/str_format/parser.h" + PUBLIC_LIBRARIES + str_format_extension_internal + absl::strings + absl::base + absl::numeric + absl::inlined_vector + absl::span +) + +# str_format_extension_internal +absl_library( + TARGET + str_format_extension_internal + SOURCES + "internal/str_format/extension.cc" + "internal/str_format/extension.h" + "internal/str_format/output.cc" + "internal/str_format/output.h" + PUBLIC_LIBRARIES + absl::base + absl::strings +) + +# pow10_helper +absl_library( + TARGET + pow10_helper + SOURCES + "internal/pow10_helper.cc" + "internal/pow10_helper.h" +) # ## TESTS @@ -158,7 +216,7 @@ absl_test( # test string_view_test set(STRING_VIEW_TEST_SRC "string_view_test.cc") -set(STRING_VIEW_TEST_PUBLIC_LIBRARIES absl::strings absl_throw_delegate absl::base) +set(STRING_VIEW_TEST_PUBLIC_LIBRARIES absl::strings absl_internal_throw_delegate absl::base) absl_test( TARGET @@ -186,7 +244,7 @@ absl_test( # test str_replace_test set(STR_REPLACE_TEST_SRC "str_replace_test.cc") -set(STR_REPLACE_TEST_PUBLIC_LIBRARIES absl::strings absl::base absl_throw_delegate) +set(STR_REPLACE_TEST_PUBLIC_LIBRARIES absl::strings absl::base absl_internal_throw_delegate) absl_test( TARGET @@ -200,7 +258,7 @@ absl_test( # test str_split_test set(STR_SPLIT_TEST_SRC "str_split_test.cc") -set(STR_SPLIT_TEST_PUBLIC_LIBRARIES absl::strings absl::base absl_throw_delegate) +set(STR_SPLIT_TEST_PUBLIC_LIBRARIES absl::strings absl::base absl_internal_throw_delegate) absl_test( TARGET @@ -267,7 +325,7 @@ absl_test( # test numbers_test set(NUMBERS_TEST_SRC "numbers_test.cc") -set(NUMBERS_TEST_PUBLIC_LIBRARIES absl::strings) +set(NUMBERS_TEST_PUBLIC_LIBRARIES absl::strings pow10_helper) absl_test( TARGET @@ -309,7 +367,7 @@ absl_test( # test charconv_test set(CHARCONV_TEST_SRC "charconv_test.cc") -set(CHARCONV_TEST_PUBLIC_LIBRARIES absl::strings) +set(CHARCONV_TEST_PUBLIC_LIBRARIES absl::strings absl::str_format pow10_helper) absl_test( TARGET @@ -347,3 +405,77 @@ absl_test( PUBLIC_LIBRARIES ${CHARCONV_BIGINT_TEST_PUBLIC_LIBRARIES} ) +# test str_format_test +absl_test( + TARGET + str_format_test + SOURCES + "str_format_test.cc" + PUBLIC_LIBRARIES + absl::base + absl::str_format + absl::strings +) + +# test str_format_bind_test +absl_test( + TARGET + str_format_bind_test + SOURCES + "internal/str_format/bind_test.cc" + PUBLIC_LIBRARIES + str_format_internal +) + +# test str_format_checker_test +absl_test( + TARGET + str_format_checker_test + SOURCES + "internal/str_format/checker_test.cc" + PUBLIC_LIBRARIES + absl::str_format +) + +# test str_format_convert_test +absl_test( + TARGET + str_format_convert_test + SOURCES + "internal/str_format/convert_test.cc" + PUBLIC_LIBRARIES + str_format_internal + absl::numeric +) + +# test str_format_output_test +absl_test( + TARGET + str_format_output_test + SOURCES + "internal/str_format/output_test.cc" + PUBLIC_LIBRARIES + str_format_extension_internal +) + +# test str_format_parser_test +absl_test( + TARGET + str_format_parser_test + SOURCES + "internal/str_format/parser_test.cc" + PUBLIC_LIBRARIES + str_format_internal + absl::base +) + +# test pow10_helper_test +absl_test( + TARGET + pow10_helper_test + SOURCES + "internal/pow10_helper_test.cc" + PUBLIC_LIBRARIES + pow10_helper + absl::str_format +) diff --git a/absl/strings/ascii.cc b/absl/strings/ascii.cc index 5c94acc7..5d08e816 100644 --- a/absl/strings/ascii.cc +++ b/absl/strings/ascii.cc @@ -15,7 +15,7 @@ #include "absl/strings/ascii.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace ascii_internal { // # Table generated by this Python code (bit 0x02 is currently unused): @@ -196,5 +196,5 @@ void RemoveExtraAsciiWhitespace(std::string* str) { str->erase(output_it - &(*str)[0]); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/ascii.h b/absl/strings/ascii.h index 507fad4b..98418fd2 100644 --- a/absl/strings/ascii.h +++ b/absl/strings/ascii.h @@ -59,7 +59,7 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace ascii_internal { // Declaration for an array of bitfields holding character information. @@ -166,7 +166,7 @@ inline char ascii_tolower(unsigned char c) { // Converts the characters in `s` to lowercase, changing the contents of `s`. void AsciiStrToLower(std::string* s); -// Creates a lowercase std::string from a given absl::string_view. +// Creates a lowercase string from a given absl::string_view. ABSL_MUST_USE_RESULT inline std::string AsciiStrToLower(absl::string_view s) { std::string result(s); absl::AsciiStrToLower(&result); @@ -184,7 +184,7 @@ inline char ascii_toupper(unsigned char c) { // Converts the characters in `s` to uppercase, changing the contents of `s`. void AsciiStrToUpper(std::string* s); -// Creates an uppercase std::string from a given absl::string_view. +// Creates an uppercase string from a given absl::string_view. ABSL_MUST_USE_RESULT inline std::string AsciiStrToUpper(absl::string_view s) { std::string result(s); absl::AsciiStrToUpper(&result); @@ -196,10 +196,10 @@ ABSL_MUST_USE_RESULT inline std::string AsciiStrToUpper(absl::string_view s) { ABSL_MUST_USE_RESULT inline absl::string_view StripLeadingAsciiWhitespace( absl::string_view str) { auto it = std::find_if_not(str.begin(), str.end(), absl::ascii_isspace); - return absl::string_view(it, str.end() - it); + return str.substr(it - str.begin()); } -// Strips in place whitespace from the beginning of the given std::string. +// Strips in place whitespace from the beginning of the given string. inline void StripLeadingAsciiWhitespace(std::string* str) { auto it = std::find_if_not(str->begin(), str->end(), absl::ascii_isspace); str->erase(str->begin(), it); @@ -210,10 +210,10 @@ inline void StripLeadingAsciiWhitespace(std::string* str) { ABSL_MUST_USE_RESULT inline absl::string_view StripTrailingAsciiWhitespace( absl::string_view str) { auto it = std::find_if_not(str.rbegin(), str.rend(), absl::ascii_isspace); - return absl::string_view(str.begin(), str.rend() - it); + return str.substr(0, str.rend() - it); } -// Strips in place whitespace from the end of the given std::string +// Strips in place whitespace from the end of the given string inline void StripTrailingAsciiWhitespace(std::string* str) { auto it = std::find_if_not(str->rbegin(), str->rend(), absl::ascii_isspace); str->erase(str->rend() - it); @@ -226,7 +226,7 @@ ABSL_MUST_USE_RESULT inline absl::string_view StripAsciiWhitespace( return StripTrailingAsciiWhitespace(StripLeadingAsciiWhitespace(str)); } -// Strips in place whitespace from both ends of the given std::string +// Strips in place whitespace from both ends of the given string inline void StripAsciiWhitespace(std::string* str) { StripTrailingAsciiWhitespace(str); StripLeadingAsciiWhitespace(str); @@ -235,7 +235,7 @@ inline void StripAsciiWhitespace(std::string* str) { // Removes leading, trailing, and consecutive internal whitespace. void RemoveExtraAsciiWhitespace(std::string*); -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_ASCII_H_ diff --git a/absl/strings/ascii_benchmark.cc b/absl/strings/ascii_benchmark.cc new file mode 100644 index 00000000..8dea4b8c --- /dev/null +++ b/absl/strings/ascii_benchmark.cc @@ -0,0 +1,120 @@ +// Copyright 2018 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/ascii.h" + +#include <cctype> +#include <string> +#include <array> +#include <random> + +#include "benchmark/benchmark.h" + +namespace { + +std::array<unsigned char, 256> MakeShuffledBytes() { + std::array<unsigned char, 256> bytes; + for (size_t i = 0; i < 256; ++i) bytes[i] = static_cast<unsigned char>(i); + std::random_device rd; + std::seed_seq seed({rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()}); + std::mt19937 g(seed); + std::shuffle(bytes.begin(), bytes.end(), g); + return bytes; +} + +template <typename Function> +void AsciiBenchmark(benchmark::State& state, Function f) { + std::array<unsigned char, 256> bytes = MakeShuffledBytes(); + size_t sum = 0; + for (auto _ : state) { + for (unsigned char b : bytes) sum += f(b) ? 1 : 0; + } + // Make a copy of `sum` before calling `DoNotOptimize` to make sure that `sum` + // can be put in a CPU register and not degrade performance in the loop above. + size_t sum2 = sum; + benchmark::DoNotOptimize(sum2); + state.SetBytesProcessed(state.iterations() * bytes.size()); +} + +using StdAsciiFunction = int (*)(int); +template <StdAsciiFunction f> +void BM_Ascii(benchmark::State& state) { + AsciiBenchmark(state, f); +} + +using AbslAsciiIsFunction = bool (*)(unsigned char); +template <AbslAsciiIsFunction f> +void BM_Ascii(benchmark::State& state) { + AsciiBenchmark(state, f); +} + +using AbslAsciiToFunction = char (*)(unsigned char); +template <AbslAsciiToFunction f> +void BM_Ascii(benchmark::State& state) { + AsciiBenchmark(state, f); +} + +inline char Noop(unsigned char b) { return static_cast<char>(b); } + +BENCHMARK_TEMPLATE(BM_Ascii, Noop); +BENCHMARK_TEMPLATE(BM_Ascii, std::isalpha); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isalpha); +BENCHMARK_TEMPLATE(BM_Ascii, std::isdigit); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isdigit); +BENCHMARK_TEMPLATE(BM_Ascii, std::isalnum); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isalnum); +BENCHMARK_TEMPLATE(BM_Ascii, std::isspace); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isspace); +BENCHMARK_TEMPLATE(BM_Ascii, std::ispunct); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_ispunct); +BENCHMARK_TEMPLATE(BM_Ascii, std::isblank); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isblank); +BENCHMARK_TEMPLATE(BM_Ascii, std::iscntrl); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_iscntrl); +BENCHMARK_TEMPLATE(BM_Ascii, std::isxdigit); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isxdigit); +BENCHMARK_TEMPLATE(BM_Ascii, std::isprint); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isprint); +BENCHMARK_TEMPLATE(BM_Ascii, std::isgraph); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isgraph); +BENCHMARK_TEMPLATE(BM_Ascii, std::isupper); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isupper); +BENCHMARK_TEMPLATE(BM_Ascii, std::islower); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_islower); +BENCHMARK_TEMPLATE(BM_Ascii, isascii); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_isascii); +BENCHMARK_TEMPLATE(BM_Ascii, std::tolower); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_tolower); +BENCHMARK_TEMPLATE(BM_Ascii, std::toupper); +BENCHMARK_TEMPLATE(BM_Ascii, absl::ascii_toupper); + +static void BM_StrToLower(benchmark::State& state) { + const int size = state.range(0); + std::string s(size, 'X'); + for (auto _ : state) { + benchmark::DoNotOptimize(absl::AsciiStrToLower(s)); + } +} +BENCHMARK(BM_StrToLower)->Range(1, 1 << 20); + +static void BM_StrToUpper(benchmark::State& state) { + const int size = state.range(0); + std::string s(size, 'x'); + for (auto _ : state) { + benchmark::DoNotOptimize(absl::AsciiStrToUpper(s)); + } +} +BENCHMARK(BM_StrToUpper)->Range(1, 1 << 20); + +} // namespace diff --git a/absl/strings/charconv.cc b/absl/strings/charconv.cc index ab75ab49..21ea17b1 100644 --- a/absl/strings/charconv.cc +++ b/absl/strings/charconv.cc @@ -20,8 +20,8 @@ #include <cstring> #include "absl/base/casts.h" +#include "absl/base/internal/bits.h" #include "absl/numeric/int128.h" -#include "absl/strings/internal/bits.h" #include "absl/strings/internal/charconv_bigint.h" #include "absl/strings/internal/charconv_parse.h" @@ -57,7 +57,7 @@ // narrower mantissas. namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace { template <typename FloatType> @@ -244,9 +244,9 @@ struct CalculatedFloat { // minus the number of leading zero bits.) int BitWidth(uint128 value) { if (Uint128High64(value) == 0) { - return 64 - strings_internal::CountLeadingZeros64(Uint128Low64(value)); + return 64 - base_internal::CountLeadingZeros64(Uint128Low64(value)); } - return 128 - strings_internal::CountLeadingZeros64(Uint128High64(value)); + return 128 - base_internal::CountLeadingZeros64(Uint128High64(value)); } // Calculates how far to the right a mantissa needs to be shifted to create a @@ -519,7 +519,7 @@ CalculatedFloat CalculateFromParsedHexadecimal( const strings_internal::ParsedFloat& parsed_hex) { uint64_t mantissa = parsed_hex.mantissa; int exponent = parsed_hex.exponent; - int mantissa_width = 64 - strings_internal::CountLeadingZeros64(mantissa); + int mantissa_width = 64 - base_internal::CountLeadingZeros64(mantissa); const int shift = NormalizedShiftSize<FloatType>(mantissa_width, exponent); bool result_exact; exponent += shift; @@ -980,5 +980,5 @@ const int16_t kPower10ExponentTable[] = { }; } // namespace -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/charconv.h b/absl/strings/charconv.h index fe2dee37..160306e6 100644 --- a/absl/strings/charconv.h +++ b/absl/strings/charconv.h @@ -18,12 +18,12 @@ #include <system_error> // NOLINT(build/c++11) namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // Workalike compatibilty version of std::chars_format from C++17. // // This is an bitfield enumerator which can be passed to absl::from_chars to -// configure the std::string-to-float conversion. +// configure the string-to-float conversion. enum class chars_format { scientific = 1, fixed = 2, @@ -31,7 +31,7 @@ enum class chars_format { general = fixed | scientific, }; -// The return result of a std::string-to-number conversion. +// The return result of a string-to-number conversion. // // `ec` will be set to `invalid_argument` if a well-formed number was not found // at the start of the input range, `result_out_of_range` if a well-formed @@ -68,7 +68,7 @@ struct from_chars_result { // If `fmt` is set, it must be one of the enumerator values of the chars_format. // (This is despite the fact that chars_format is a bitmask type.) If set to // `scientific`, a matching number must contain an exponent. If set to `fixed`, -// then an exponent will never match. (For example, the std::string "1e5" will be +// then an exponent will never match. (For example, the string "1e5" will be // parsed as "1".) If set to `hex`, then a hexadecimal float is parsed in the // format that strtod() accepts, except that a "0x" prefix is NOT matched. // (In particular, in `hex` mode, the input "0xff" results in the largest @@ -111,7 +111,7 @@ inline chars_format& operator^=(chars_format& lhs, chars_format rhs) { return lhs; } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_CHARCONV_H_ diff --git a/absl/strings/charconv_test.cc b/absl/strings/charconv_test.cc index f8d71cc6..d07537eb 100644 --- a/absl/strings/charconv_test.cc +++ b/absl/strings/charconv_test.cc @@ -19,7 +19,9 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/internal/pow10_helper.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #ifdef _MSC_FULL_VER #define ABSL_COMPILER_DOES_EXACT_ROUNDING 0 @@ -31,9 +33,11 @@ namespace { +using absl::strings_internal::Pow10; + #if ABSL_COMPILER_DOES_EXACT_ROUNDING -// Tests that the given std::string is accepted by absl::from_chars, and that it +// Tests that the given string is accepted by absl::from_chars, and that it // converts exactly equal to the given number. void TestDoubleParse(absl::string_view str, double expected_number) { SCOPED_TRACE(str); @@ -250,7 +254,7 @@ TEST(FromChars, NearRoundingCasesExplicit) { EXPECT_EQ(ToFloat("459926601011.e15"), ldexpf(12466336, 65)); } -// Common test logic for converting a std::string which lies exactly halfway between +// Common test logic for converting a string which lies exactly halfway between // two target floats. // // mantissa and exponent represent the precise value between two floating point @@ -655,7 +659,7 @@ int NextStep(int step) { // is correct for in-bounds values, and that overflow and underflow are done // correctly for out-of-bounds values. // -// input_generator maps from an integer index to a std::string to test. +// input_generator maps from an integer index to a string to test. // expected_generator maps from an integer index to an expected Float value. // from_chars conversion of input_generator(i) should result in // expected_generator(i). @@ -678,7 +682,8 @@ void TestOverflowAndUnderflow( auto result = absl::from_chars(input.data(), input.data() + input.size(), actual); EXPECT_EQ(result.ec, std::errc()); - EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, actual) + << absl::StrFormat("%a vs %a", expected, actual); } // test legal values near upper_bound for (index = upper_bound, step = 1; index > lower_bound; @@ -690,7 +695,8 @@ void TestOverflowAndUnderflow( auto result = absl::from_chars(input.data(), input.data() + input.size(), actual); EXPECT_EQ(result.ec, std::errc()); - EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, actual) + << absl::StrFormat("%a vs %a", expected, actual); } // Test underflow values below lower_bound for (index = lower_bound - 1, step = 1; index > -1000000; @@ -747,7 +753,7 @@ TEST(FromChars, HexdecimalFloatLimits) { // acceptable exponents in this test. TEST(FromChars, DecimalDoubleLimits) { auto input_gen = [](int index) { return absl::StrCat("1.0e", index); }; - auto expected_gen = [](int index) { return std::pow(10.0, index); }; + auto expected_gen = [](int index) { return Pow10(index); }; TestOverflowAndUnderflow<double>(input_gen, expected_gen, -323, 308); } @@ -759,7 +765,7 @@ TEST(FromChars, DecimalDoubleLimits) { // acceptable exponents in this test. TEST(FromChars, DecimalFloatLimits) { auto input_gen = [](int index) { return absl::StrCat("1.0e", index); }; - auto expected_gen = [](int index) { return std::pow(10.0, index); }; + auto expected_gen = [](int index) { return Pow10(index); }; TestOverflowAndUnderflow<float>(input_gen, expected_gen, -45, 38); } diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc index 9874f492..69053c19 100644 --- a/absl/strings/escaping.cc +++ b/absl/strings/escaping.cc @@ -33,7 +33,7 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace { // Digit conversion. @@ -90,7 +90,7 @@ inline bool IsSurrogate(char32_t c, absl::string_view src, std::string* error) { // // Unescapes C escape sequences and is the reverse of CEscape(). // -// If 'source' is valid, stores the unescaped std::string and its size in +// If 'source' is valid, stores the unescaped string and its size in // 'dest' and 'dest_len' respectively, and returns true. Otherwise // returns false and optionally stores the error description in // 'error'. Set 'error' to nullptr to disable error reporting. @@ -105,7 +105,7 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, char* dest, ptrdiff_t* dest_len, std::string* error) { char* d = dest; const char* p = source.data(); - const char* end = source.end(); + const char* end = p + source.size(); const char* last_byte = end - 1; // Small optimization for case where source = dest and there's no escaping @@ -295,7 +295,7 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, // ---------------------------------------------------------------------- // CUnescapeInternal() // -// Same as above but uses a C++ std::string for output. 'source' and 'dest' +// Same as above but uses a C++ string for output. 'source' and 'dest' // may be the same. // ---------------------------------------------------------------------- bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, @@ -305,7 +305,7 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, ptrdiff_t dest_size; if (!CUnescapeInternal(source, leave_nulls_escaped, - const_cast<char*>(dest->data()), + &(*dest)[0], &dest_size, error)) { return false; @@ -685,7 +685,7 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, // The arrays below were generated by the following code // #include <sys/time.h> // #include <stdlib.h> -// #include <std::string.h> +// #include <string.h> // main() // { // static const char Base64[] = @@ -940,7 +940,8 @@ constexpr char kBase64Chars[] = constexpr char kWebSafeBase64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; -void Base64EscapeInternal(const unsigned char* src, size_t szsrc, std::string* dest, +template <typename String> +void Base64EscapeInternal(const unsigned char* src, size_t szsrc, String* dest, bool do_padding, const char* base64_chars) { const size_t calc_escaped_size = CalculateBase64EscapedLenInternal(szsrc, do_padding); @@ -952,7 +953,8 @@ void Base64EscapeInternal(const unsigned char* src, size_t szsrc, std::string* d dest->erase(escaped_len); } -bool Base64UnescapeInternal(const char* src, size_t slen, std::string* dest, +template <typename String> +bool Base64UnescapeInternal(const char* src, size_t slen, String* dest, const signed char* unbase64) { // Determine the size of the output std::string. Base64 encodes every 3 bytes into // 4 characters. any leftover chars are added directly for good measure. @@ -1000,7 +1002,7 @@ constexpr char kHexValue[256] = { /* clang-format on */ // This is a templated function so that T can be either a char* -// or a std::string. This works because we use the [] operator to access +// or a string. This works because we use the [] operator to access // individual characters at a time. template <typename T> void HexStringToBytesInternal(const char* from, T to, ptrdiff_t num) { @@ -1010,7 +1012,7 @@ void HexStringToBytesInternal(const char* from, T to, ptrdiff_t num) { } } -// This is a templated function so that T can be either a char* or a std::string. +// This is a templated function so that T can be either a char* or a string. template <typename T> void BytesToHexStringInternal(const unsigned char* src, T dest, ptrdiff_t num) { auto dest_ptr = &dest[0]; @@ -1107,5 +1109,5 @@ std::string BytesToHexString(absl::string_view from) { return result; } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/escaping.h b/absl/strings/escaping.h index 16326a9c..a31fb374 100644 --- a/absl/strings/escaping.h +++ b/absl/strings/escaping.h @@ -17,7 +17,7 @@ // File: escaping.h // ----------------------------------------------------------------------------- // -// This header file contains std::string utilities involved in escaping and +// This header file contains string utilities involved in escaping and // unescaping strings in various ways. // @@ -34,11 +34,11 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // CUnescape() // -// Unescapes a `source` std::string and copies it into `dest`, rewriting C-style +// Unescapes a `source` string and copies it into `dest`, rewriting C-style // escape sequences (http://en.cppreference.com/w/cpp/language/escape) into // their proper code point equivalents, returning `true` if successful. // @@ -58,9 +58,10 @@ inline namespace lts_2018_06_20 { // 0x99). // // -// If any errors are encountered, this function returns `false` and stores the -// first encountered error in `error`. To disable error reporting, set `error` -// to `nullptr` or use the overload with no error reporting below. +// If any errors are encountered, this function returns `false`, leaving the +// `dest` output parameter in an unspecified state, and stores the first +// encountered error in `error`. To disable error reporting, set `error` to +// `nullptr` or use the overload with no error reporting below. // // Example: // @@ -79,7 +80,7 @@ inline bool CUnescape(absl::string_view source, std::string* dest) { // CEscape() // -// Escapes a 'src' std::string using C-style escapes sequences +// Escapes a 'src' string using C-style escapes sequences // (http://en.cppreference.com/w/cpp/language/escape), escaping other // non-printable/non-whitespace bytes as octal sequences (e.g. "\377"). // @@ -92,7 +93,7 @@ std::string CEscape(absl::string_view src); // CHexEscape() // -// Escapes a 'src' std::string using C-style escape sequences, escaping +// Escapes a 'src' string using C-style escape sequences, escaping // other non-printable/non-whitespace bytes as hexadecimal sequences (e.g. // "\xFF"). // @@ -105,7 +106,7 @@ std::string CHexEscape(absl::string_view src); // Utf8SafeCEscape() // -// Escapes a 'src' std::string using C-style escape sequences, escaping bytes as +// Escapes a 'src' string using C-style escape sequences, escaping bytes as // octal sequences, and passing through UTF-8 characters without conversion. // I.e., when encountering any bytes with their high bit set, this function // will not escape those values, whether or not they are valid UTF-8. @@ -113,51 +114,51 @@ std::string Utf8SafeCEscape(absl::string_view src); // Utf8SafeCHexEscape() // -// Escapes a 'src' std::string using C-style escape sequences, escaping bytes as +// Escapes a 'src' string using C-style escape sequences, escaping bytes as // hexadecimal sequences, and passing through UTF-8 characters without // conversion. std::string Utf8SafeCHexEscape(absl::string_view src); // Base64Unescape() // -// Converts a `src` std::string encoded in Base64 to its binary equivalent, writing +// Converts a `src` string encoded in Base64 to its binary equivalent, writing // it to a `dest` buffer, returning `true` on success. If `src` contains invalid // characters, `dest` is cleared and returns `false`. bool Base64Unescape(absl::string_view src, std::string* dest); -// WebSafeBase64Unescape(absl::string_view, std::string*) +// WebSafeBase64Unescape() // -// Converts a `src` std::string encoded in Base64 to its binary equivalent, writing +// Converts a `src` string encoded in Base64 to its binary equivalent, writing // it to a `dest` buffer, but using '-' instead of '+', and '_' instead of '/'. // If `src` contains invalid characters, `dest` is cleared and returns `false`. bool WebSafeBase64Unescape(absl::string_view src, std::string* dest); // Base64Escape() // -// Encodes a `src` std::string into a `dest` buffer using base64 encoding, with +// Encodes a `src` string into a `dest` buffer using base64 encoding, with // padding characters. This function conforms with RFC 4648 section 4 (base64). void Base64Escape(absl::string_view src, std::string* dest); // WebSafeBase64Escape() // -// Encodes a `src` std::string into a `dest` buffer using uses '-' instead of '+' and +// Encodes a `src` string into a `dest` buffer using '-' instead of '+' and // '_' instead of '/', and without padding. This function conforms with RFC 4648 // section 5 (base64url). void WebSafeBase64Escape(absl::string_view src, std::string* dest); // HexStringToBytes() // -// Converts an ASCII hex std::string into bytes, returning binary data of length +// Converts an ASCII hex string into bytes, returning binary data of length // `from.size()/2`. std::string HexStringToBytes(absl::string_view from); // BytesToHexString() // -// Converts binary data into an ASCII text std::string, returning a std::string of size +// Converts binary data into an ASCII text string, returning a string of size // `2*from.size()`. std::string BytesToHexString(absl::string_view from); -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_ESCAPING_H_ diff --git a/absl/strings/escaping_test.cc b/absl/strings/escaping_test.cc index 3f65ec10..9dc27f3f 100644 --- a/absl/strings/escaping_test.cc +++ b/absl/strings/escaping_test.cc @@ -543,18 +543,19 @@ static struct { {"abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="}, }; -TEST(Base64, EscapeAndUnescape) { +template <typename StringType> +void TestEscapeAndUnescape() { // Check the short strings; this tests the math (and boundaries) for (const auto& tc : base64_tests) { - std::string encoded("this junk should be ignored"); + StringType encoded("this junk should be ignored"); absl::Base64Escape(tc.plaintext, &encoded); EXPECT_EQ(encoded, tc.cyphertext); - std::string decoded("this junk should be ignored"); + StringType decoded("this junk should be ignored"); EXPECT_TRUE(absl::Base64Unescape(encoded, &decoded)); EXPECT_EQ(decoded, tc.plaintext); - std::string websafe(tc.cyphertext); + StringType websafe(tc.cyphertext); for (int c = 0; c < websafe.size(); ++c) { if ('+' == websafe[c]) websafe[c] = '-'; if ('/' == websafe[c]) websafe[c] = '_'; @@ -576,7 +577,7 @@ TEST(Base64, EscapeAndUnescape) { // Now try the long strings, this tests the streaming for (const auto& tc : absl::strings_internal::base64_strings()) { - std::string buffer; + StringType buffer; absl::WebSafeBase64Escape(tc.plaintext, &buffer); EXPECT_EQ(tc.cyphertext, buffer); } @@ -586,7 +587,7 @@ TEST(Base64, EscapeAndUnescape) { absl::string_view data_set[] = {"ab-/", absl::string_view("\0bcd", 4), absl::string_view("abc.\0", 5)}; for (absl::string_view bad_data : data_set) { - std::string buf; + StringType buf; EXPECT_FALSE(absl::Base64Unescape(bad_data, &buf)); EXPECT_FALSE(absl::WebSafeBase64Unescape(bad_data, &buf)); EXPECT_TRUE(buf.empty()); @@ -594,6 +595,10 @@ TEST(Base64, EscapeAndUnescape) { } } +TEST(Base64, EscapeAndUnescape) { + TestEscapeAndUnescape<std::string>(); +} + TEST(Base64, DISABLED_HugeData) { const size_t kSize = size_t(3) * 1000 * 1000 * 1000; static_assert(kSize % 3 == 0, "kSize must be divisible by 3"); diff --git a/absl/strings/internal/bits.h b/absl/strings/internal/bits.h deleted file mode 100644 index a7d0ee5b..00000000 --- a/absl/strings/internal/bits.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef ABSL_STRINGS_INTERNAL_BITS_H_ -#define ABSL_STRINGS_INTERNAL_BITS_H_ - -#include <cstdint> - -#if defined(_MSC_VER) && defined(_M_X64) -#include <intrin.h> -#pragma intrinsic(_BitScanReverse64) -#endif - -namespace absl { -inline namespace lts_2018_06_20 { -namespace strings_internal { - -// Returns the number of leading 0 bits in a 64-bit value. -inline int CountLeadingZeros64(uint64_t n) { -#if defined(__GNUC__) - static_assert(sizeof(unsigned long long) == sizeof(n), // NOLINT(runtime/int) - "__builtin_clzll does not take 64bit arg"); - return n == 0 ? 64 : __builtin_clzll(n); -#elif defined(_MSC_VER) && defined(_M_X64) - unsigned long result; // NOLINT(runtime/int) - if (_BitScanReverse64(&result, n)) { - return 63 - result; - } - return 64; -#else - int zeroes = 60; - if (n >> 32) zeroes -= 32, n >>= 32; - if (n >> 16) zeroes -= 16, n >>= 16; - if (n >> 8) zeroes -= 8, n >>= 8; - if (n >> 4) zeroes -= 4, n >>= 4; - return "\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0\0"[n] + zeroes; -#endif -} - -} // namespace strings_internal -} // inline namespace lts_2018_06_20 -} // namespace absl - -#endif // ABSL_STRINGS_INTERNAL_BITS_H_ diff --git a/absl/strings/internal/char_map.h b/absl/strings/internal/char_map.h index e1280b03..10b7d007 100644 --- a/absl/strings/internal/char_map.h +++ b/absl/strings/internal/char_map.h @@ -28,7 +28,7 @@ #include "absl/base/port.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { class Charmap { @@ -150,7 +150,7 @@ constexpr Charmap GraphCharmap() { return PrintCharmap() & ~SpaceCharmap(); } constexpr Charmap PunctCharmap() { return GraphCharmap() & ~AlnumCharmap(); } } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_CHAR_MAP_H_ diff --git a/absl/strings/internal/charconv_bigint.cc b/absl/strings/internal/charconv_bigint.cc index eb2e2976..dac907e2 100644 --- a/absl/strings/internal/charconv_bigint.cc +++ b/absl/strings/internal/charconv_bigint.cc @@ -19,7 +19,7 @@ #include <string> namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { namespace { @@ -355,5 +355,5 @@ template class BigUnsigned<4>; template class BigUnsigned<84>; } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/internal/charconv_bigint.h b/absl/strings/internal/charconv_bigint.h index 9827b56e..ffafc11c 100644 --- a/absl/strings/internal/charconv_bigint.h +++ b/absl/strings/internal/charconv_bigint.h @@ -25,7 +25,7 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { // The largest power that 5 that can be raised to, and still fit in a uint32_t. @@ -58,17 +58,10 @@ class BigUnsigned { "unsupported max_words value"); BigUnsigned() : size_(0), words_{} {} - explicit BigUnsigned(uint32_t v) : size_(v > 0 ? 1 : 0), words_{v} {} - explicit BigUnsigned(uint64_t v) - : size_(0), - words_{static_cast<uint32_t>(v & 0xffffffff), - static_cast<uint32_t>(v >> 32)} { - if (words_[1]) { - size_ = 2; - } else if (words_[0]) { - size_ = 1; - } - } + explicit constexpr BigUnsigned(uint64_t v) + : size_((v >> 32) ? 2 : v ? 1 : 0), + words_{static_cast<uint32_t>(v & 0xffffffffu), + static_cast<uint32_t>(v >> 32)} {} // Constructs a BigUnsigned from the given string_view containing a decimal // value. If the input std::string is not a decimal integer, constructs a 0 @@ -422,7 +415,7 @@ extern template class BigUnsigned<4>; extern template class BigUnsigned<84>; } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_CHARCONV_BIGINT_H_ diff --git a/absl/strings/internal/charconv_bigint_test.cc b/absl/strings/internal/charconv_bigint_test.cc index 118b0dcb..dbab3208 100644 --- a/absl/strings/internal/charconv_bigint_test.cc +++ b/absl/strings/internal/charconv_bigint_test.cc @@ -19,7 +19,7 @@ #include "gtest/gtest.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { TEST(BigUnsigned, ShiftLeft) { @@ -201,5 +201,5 @@ TEST(BigUnsigned, TenToTheNth) { } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/internal/charconv_parse.cc b/absl/strings/internal/charconv_parse.cc index 37d75635..68d65a8a 100644 --- a/absl/strings/internal/charconv_parse.cc +++ b/absl/strings/internal/charconv_parse.cc @@ -22,7 +22,7 @@ #include "absl/strings/internal/memutil.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace { // ParseFloat<10> will read the first 19 significant digits of the mantissa. @@ -92,7 +92,7 @@ static_assert(std::numeric_limits<int>::digits10 >= kDecimalExponentDigitsMax, // To avoid incredibly large inputs causing integer overflow for our exponent, // we impose an arbitrary but very large limit on the number of significant -// digits we will accept. The implementation refuses to match a std::string with +// digits we will accept. The implementation refuses to match a string with // more consecutive significant mantissa digits than this. constexpr int kDecimalDigitLimit = 50000000; @@ -494,5 +494,5 @@ template ParsedFloat ParseFloat<16>(const char* begin, const char* end, chars_format format_flags); } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/internal/charconv_parse.h b/absl/strings/internal/charconv_parse.h index 41f4f40d..17d5a8f8 100644 --- a/absl/strings/internal/charconv_parse.h +++ b/absl/strings/internal/charconv_parse.h @@ -20,7 +20,7 @@ #include "absl/strings/charconv.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { // Enum indicating whether a parsed float is a number or special value. @@ -93,6 +93,6 @@ extern template ParsedFloat ParseFloat<16>(const char* begin, const char* end, absl::chars_format format_flags); } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_CHARCONV_PARSE_H_ diff --git a/absl/strings/internal/charconv_parse_test.cc b/absl/strings/internal/charconv_parse_test.cc index 1ff86004..f48b9aee 100644 --- a/absl/strings/internal/charconv_parse_test.cc +++ b/absl/strings/internal/charconv_parse_test.cc @@ -29,16 +29,16 @@ using absl::strings_internal::ParseFloat; namespace { -// Check that a given std::string input is parsed to the expected mantissa and +// Check that a given string input is parsed to the expected mantissa and // exponent. // -// Input std::string `s` must contain a '$' character. It marks the end of the +// Input string `s` must contain a '$' character. It marks the end of the // characters that should be consumed by the match. It is stripped from the // input to ParseFloat. // -// If input std::string `s` contains '[' and ']' characters, these mark the region +// If input string `s` contains '[' and ']' characters, these mark the region // of characters that should be marked as the "subrange". For NaNs, this is -// the location of the extended NaN std::string. For numbers, this is the location +// the location of the extended NaN string. For numbers, this is the location // of the full, over-large mantissa. template <int base> void ExpectParsedFloat(std::string s, absl::chars_format format_flags, @@ -92,10 +92,10 @@ void ExpectParsedFloat(std::string s, absl::chars_format format_flags, EXPECT_EQ(characters_matched, expected_characters_matched); } -// Check that a given std::string input is parsed to the expected mantissa and +// Check that a given string input is parsed to the expected mantissa and // exponent. // -// Input std::string `s` must contain a '$' character. It marks the end of the +// Input string `s` must contain a '$' character. It marks the end of the // characters that were consumed by the match. template <int base> void ExpectNumber(std::string s, absl::chars_format format_flags, @@ -106,7 +106,7 @@ void ExpectNumber(std::string s, absl::chars_format format_flags, expected_literal_exponent); } -// Check that a given std::string input is parsed to the given special value. +// Check that a given string input is parsed to the given special value. // // This tests against both number bases, since infinities and NaNs have // identical representations in both modes. @@ -116,7 +116,7 @@ void ExpectSpecial(const std::string& s, absl::chars_format format_flags, ExpectParsedFloat<16>(s, format_flags, type, 0, 0); } -// Check that a given input std::string is not matched by Float. +// Check that a given input string is not matched by Float. template <int base> void ExpectFailedParse(absl::string_view s, absl::chars_format format_flags) { ParsedFloat parsed = diff --git a/absl/strings/internal/escaping_test_common.h b/absl/strings/internal/escaping_test_common.h index 478e0582..50ef595f 100644 --- a/absl/strings/internal/escaping_test_common.h +++ b/absl/strings/internal/escaping_test_common.h @@ -22,7 +22,7 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { struct base64_testcase { @@ -127,7 +127,7 @@ inline const std::array<base64_testcase, 5>& base64_strings() { } } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_ESCAPING_TEST_COMMON_H_ diff --git a/absl/strings/internal/memutil.cc b/absl/strings/internal/memutil.cc index 13ad686c..1d6cfa36 100644 --- a/absl/strings/internal/memutil.cc +++ b/absl/strings/internal/memutil.cc @@ -17,7 +17,7 @@ #include <cstdlib> namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { int memcasecmp(const char* s1, const char* s2, size_t len) { @@ -108,5 +108,5 @@ const char* memmatch(const char* phaystack, size_t haylen, const char* pneedle, } } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/internal/memutil.h b/absl/strings/internal/memutil.h index 828b01d5..dcc5c9a3 100644 --- a/absl/strings/internal/memutil.h +++ b/absl/strings/internal/memutil.h @@ -14,7 +14,7 @@ // limitations under the License. // -// These routines provide mem versions of standard C std::string routines, +// These routines provide mem versions of standard C string routines, // such as strpbrk. They function exactly the same as the str versions, // so if you wonder what they are, replace the word "mem" by // "str" and check out the man page. I could return void*, as the @@ -22,14 +22,14 @@ // since this is by far the most common way these functions are called. // // The difference between the mem and str versions is the mem version -// takes a pointer and a length, rather than a '\0'-terminated std::string. +// takes a pointer and a length, rather than a '\0'-terminated string. // The memcase* routines defined here assume the locale is "C" // (they use absl::ascii_tolower instead of tolower). // // These routines are based on the BSD library. // -// Here's a list of routines from std::string.h, and their mem analogues. -// Functions in lowercase are defined in std::string.h; those in UPPERCASE +// Here's a list of routines from string.h, and their mem analogues. +// Functions in lowercase are defined in string.h; those in UPPERCASE // are defined here: // // strlen -- @@ -69,7 +69,7 @@ #include "absl/strings/ascii.h" // for absl::ascii_tolower namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { inline char* memcat(char* dest, size_t destlen, const char* src, @@ -142,7 +142,7 @@ const char* memmatch(const char* phaystack, size_t haylen, const char* pneedle, size_t neelen); } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_MEMUTIL_H_ diff --git a/absl/strings/internal/numbers_test_common.h b/absl/strings/internal/numbers_test_common.h index a511dcf7..32aa0bfa 100644 --- a/absl/strings/internal/numbers_test_common.h +++ b/absl/strings/internal/numbers_test_common.h @@ -24,7 +24,7 @@ #include <string> namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { template <typename IntType> @@ -65,11 +65,11 @@ struct uint32_test_case { inline const std::array<uint32_test_case, 27>& strtouint32_test_cases() { static const std::array<uint32_test_case, 27> test_cases{{ - {"0xffffffff", true, 16, std::numeric_limits<uint32_t>::max()}, + {"0xffffffff", true, 16, (std::numeric_limits<uint32_t>::max)()}, {"0x34234324", true, 16, 0x34234324}, {"34234324", true, 16, 0x34234324}, {"0", true, 16, 0}, - {" \t\n 0xffffffff", true, 16, std::numeric_limits<uint32_t>::max()}, + {" \t\n 0xffffffff", true, 16, (std::numeric_limits<uint32_t>::max)()}, {" \f\v 46", true, 10, 46}, // must accept weird whitespace {" \t\n 72717222", true, 8, 072717222}, {" \t\n 072717222", true, 8, 072717222}, @@ -78,7 +78,7 @@ inline const std::array<uint32_test_case, 27>& strtouint32_test_cases() { // Base-10 version. {"34234324", true, 0, 34234324}, - {"4294967295", true, 0, std::numeric_limits<uint32_t>::max()}, + {"4294967295", true, 0, (std::numeric_limits<uint32_t>::max)()}, {"34234324 \n\t", true, 10, 34234324}, // Unusual base @@ -97,8 +97,8 @@ inline const std::array<uint32_test_case, 27>& strtouint32_test_cases() { {" \t\n -123", false, 0, 0}, // Out of bounds. - {"4294967296", false, 0, std::numeric_limits<uint32_t>::max()}, - {"0x100000000", false, 0, std::numeric_limits<uint32_t>::max()}, + {"4294967296", false, 0, (std::numeric_limits<uint32_t>::max)()}, + {"0x100000000", false, 0, (std::numeric_limits<uint32_t>::max)()}, {nullptr, false, 0, 0}, }}; return test_cases; @@ -120,7 +120,7 @@ inline const std::array<uint64_test_case, 34>& strtouint64_test_cases() { {"000", true, 0, 0}, {"0", true, 0, 0}, {" \t\n 0xffffffffffffffff", true, 16, - std::numeric_limits<uint64_t>::max()}, + (std::numeric_limits<uint64_t>::max)()}, {"012345670123456701234", true, 8, int64_t{012345670123456701234}}, {"12345670123456701234", true, 8, int64_t{012345670123456701234}}, @@ -131,7 +131,7 @@ inline const std::array<uint64_test_case, 34>& strtouint64_test_cases() { {"34234324487834466", true, 0, int64_t{34234324487834466}}, {" \t\n 18446744073709551615", true, 0, - std::numeric_limits<uint64_t>::max()}, + (std::numeric_limits<uint64_t>::max)()}, {"34234324487834466 \n\t ", true, 0, int64_t{34234324487834466}}, @@ -157,12 +157,13 @@ inline const std::array<uint64_test_case, 34>& strtouint64_test_cases() { // Out of bounds. {"18446744073709551616", false, 10, 0}, {"18446744073709551616", false, 0, 0}, - {"0x10000000000000000", false, 16, std::numeric_limits<uint64_t>::max()}, + {"0x10000000000000000", false, 16, + (std::numeric_limits<uint64_t>::max)()}, {"0X10000000000000000", false, 16, - std::numeric_limits<uint64_t>::max()}, // 0X versus 0x. - {"0x10000000000000000", false, 0, std::numeric_limits<uint64_t>::max()}, + (std::numeric_limits<uint64_t>::max)()}, // 0X versus 0x. + {"0x10000000000000000", false, 0, (std::numeric_limits<uint64_t>::max)()}, {"0X10000000000000000", false, 0, - std::numeric_limits<uint64_t>::max()}, // 0X versus 0x. + (std::numeric_limits<uint64_t>::max)()}, // 0X versus 0x. {"0x1234", true, 16, 0x1234}, @@ -174,7 +175,7 @@ inline const std::array<uint64_test_case, 34>& strtouint64_test_cases() { } } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_NUMBERS_TEST_COMMON_H_ diff --git a/absl/strings/internal/ostringstream.cc b/absl/strings/internal/ostringstream.cc index 9fe5b3c5..77f4b0b3 100644 --- a/absl/strings/internal/ostringstream.cc +++ b/absl/strings/internal/ostringstream.cc @@ -15,7 +15,7 @@ #include "absl/strings/internal/ostringstream.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { OStringStream::Buf::int_type OStringStream::overflow(int c) { @@ -32,5 +32,5 @@ std::streamsize OStringStream::xsputn(const char* s, std::streamsize n) { } } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/internal/ostringstream.h b/absl/strings/internal/ostringstream.h index 66ae3c29..908e170c 100644 --- a/absl/strings/internal/ostringstream.h +++ b/absl/strings/internal/ostringstream.h @@ -23,21 +23,21 @@ #include "absl/base/port.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { -// The same as std::ostringstream but appends to a user-specified std::string, +// The same as std::ostringstream but appends to a user-specified string, // and is faster. It is ~70% faster to create, ~50% faster to write to, and -// completely free to extract the result std::string. +// completely free to extract the result string. // -// std::string s; +// string s; // OStringStream strm(&s); // strm << 42 << ' ' << 3.14; // appends to `s` // // The stream object doesn't have to be named. Starting from C++11 operator<< // works with rvalues of std::ostream. // -// std::string s; +// string s; // OStringStream(&s) << 42 << ' ' << 3.14; // appends to `s` // // OStringStream is faster to create than std::ostringstream but it's still @@ -46,14 +46,14 @@ namespace strings_internal { // // Creates unnecessary instances of OStringStream: slow. // -// std::string s; +// string s; // OStringStream(&s) << 42; // OStringStream(&s) << ' '; // OStringStream(&s) << 3.14; // // Creates a single instance of OStringStream and reuses it: fast. // -// std::string s; +// string s; // OStringStream strm(&s); // strm << 42; // strm << ' '; @@ -83,7 +83,7 @@ class OStringStream : private std::basic_streambuf<char>, public std::ostream { }; } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_OSTRINGSTREAM_H_ diff --git a/absl/strings/internal/pow10_helper.cc b/absl/strings/internal/pow10_helper.cc new file mode 100644 index 00000000..c7f4875a --- /dev/null +++ b/absl/strings/internal/pow10_helper.cc @@ -0,0 +1,122 @@ +// Copyright 2018 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/pow10_helper.h" + +#include <cmath> + +namespace absl { +inline namespace lts_2018_12_18 { +namespace strings_internal { + +namespace { + +// The exact value of 1e23 falls precisely halfway between two representable +// doubles. Furthermore, the rounding rules we prefer (break ties by rounding +// to the nearest even) dictate in this case that the number should be rounded +// down, but this is not completely specified for floating-point literals in +// C++. (It just says to use the default rounding mode of the standard +// library.) We ensure the result we want by using a number that has an +// unambiguous correctly rounded answer. +constexpr double k1e23 = 9999999999999999e7; + +constexpr double kPowersOfTen[] = { + 0.0, 1e-323, 1e-322, 1e-321, 1e-320, 1e-319, 1e-318, 1e-317, 1e-316, + 1e-315, 1e-314, 1e-313, 1e-312, 1e-311, 1e-310, 1e-309, 1e-308, 1e-307, + 1e-306, 1e-305, 1e-304, 1e-303, 1e-302, 1e-301, 1e-300, 1e-299, 1e-298, + 1e-297, 1e-296, 1e-295, 1e-294, 1e-293, 1e-292, 1e-291, 1e-290, 1e-289, + 1e-288, 1e-287, 1e-286, 1e-285, 1e-284, 1e-283, 1e-282, 1e-281, 1e-280, + 1e-279, 1e-278, 1e-277, 1e-276, 1e-275, 1e-274, 1e-273, 1e-272, 1e-271, + 1e-270, 1e-269, 1e-268, 1e-267, 1e-266, 1e-265, 1e-264, 1e-263, 1e-262, + 1e-261, 1e-260, 1e-259, 1e-258, 1e-257, 1e-256, 1e-255, 1e-254, 1e-253, + 1e-252, 1e-251, 1e-250, 1e-249, 1e-248, 1e-247, 1e-246, 1e-245, 1e-244, + 1e-243, 1e-242, 1e-241, 1e-240, 1e-239, 1e-238, 1e-237, 1e-236, 1e-235, + 1e-234, 1e-233, 1e-232, 1e-231, 1e-230, 1e-229, 1e-228, 1e-227, 1e-226, + 1e-225, 1e-224, 1e-223, 1e-222, 1e-221, 1e-220, 1e-219, 1e-218, 1e-217, + 1e-216, 1e-215, 1e-214, 1e-213, 1e-212, 1e-211, 1e-210, 1e-209, 1e-208, + 1e-207, 1e-206, 1e-205, 1e-204, 1e-203, 1e-202, 1e-201, 1e-200, 1e-199, + 1e-198, 1e-197, 1e-196, 1e-195, 1e-194, 1e-193, 1e-192, 1e-191, 1e-190, + 1e-189, 1e-188, 1e-187, 1e-186, 1e-185, 1e-184, 1e-183, 1e-182, 1e-181, + 1e-180, 1e-179, 1e-178, 1e-177, 1e-176, 1e-175, 1e-174, 1e-173, 1e-172, + 1e-171, 1e-170, 1e-169, 1e-168, 1e-167, 1e-166, 1e-165, 1e-164, 1e-163, + 1e-162, 1e-161, 1e-160, 1e-159, 1e-158, 1e-157, 1e-156, 1e-155, 1e-154, + 1e-153, 1e-152, 1e-151, 1e-150, 1e-149, 1e-148, 1e-147, 1e-146, 1e-145, + 1e-144, 1e-143, 1e-142, 1e-141, 1e-140, 1e-139, 1e-138, 1e-137, 1e-136, + 1e-135, 1e-134, 1e-133, 1e-132, 1e-131, 1e-130, 1e-129, 1e-128, 1e-127, + 1e-126, 1e-125, 1e-124, 1e-123, 1e-122, 1e-121, 1e-120, 1e-119, 1e-118, + 1e-117, 1e-116, 1e-115, 1e-114, 1e-113, 1e-112, 1e-111, 1e-110, 1e-109, + 1e-108, 1e-107, 1e-106, 1e-105, 1e-104, 1e-103, 1e-102, 1e-101, 1e-100, + 1e-99, 1e-98, 1e-97, 1e-96, 1e-95, 1e-94, 1e-93, 1e-92, 1e-91, + 1e-90, 1e-89, 1e-88, 1e-87, 1e-86, 1e-85, 1e-84, 1e-83, 1e-82, + 1e-81, 1e-80, 1e-79, 1e-78, 1e-77, 1e-76, 1e-75, 1e-74, 1e-73, + 1e-72, 1e-71, 1e-70, 1e-69, 1e-68, 1e-67, 1e-66, 1e-65, 1e-64, + 1e-63, 1e-62, 1e-61, 1e-60, 1e-59, 1e-58, 1e-57, 1e-56, 1e-55, + 1e-54, 1e-53, 1e-52, 1e-51, 1e-50, 1e-49, 1e-48, 1e-47, 1e-46, + 1e-45, 1e-44, 1e-43, 1e-42, 1e-41, 1e-40, 1e-39, 1e-38, 1e-37, + 1e-36, 1e-35, 1e-34, 1e-33, 1e-32, 1e-31, 1e-30, 1e-29, 1e-28, + 1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21, 1e-20, 1e-19, + 1e-18, 1e-17, 1e-16, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, + 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, + 1e+0, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, + 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, + 1e+18, 1e+19, 1e+20, 1e+21, 1e+22, k1e23, 1e+24, 1e+25, 1e+26, + 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, + 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, 1e+41, 1e+42, 1e+43, 1e+44, + 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, + 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, 1e+61, 1e+62, + 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, + 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, + 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, + 1e+99, 1e+100, 1e+101, 1e+102, 1e+103, 1e+104, 1e+105, 1e+106, 1e+107, + 1e+108, 1e+109, 1e+110, 1e+111, 1e+112, 1e+113, 1e+114, 1e+115, 1e+116, + 1e+117, 1e+118, 1e+119, 1e+120, 1e+121, 1e+122, 1e+123, 1e+124, 1e+125, + 1e+126, 1e+127, 1e+128, 1e+129, 1e+130, 1e+131, 1e+132, 1e+133, 1e+134, + 1e+135, 1e+136, 1e+137, 1e+138, 1e+139, 1e+140, 1e+141, 1e+142, 1e+143, + 1e+144, 1e+145, 1e+146, 1e+147, 1e+148, 1e+149, 1e+150, 1e+151, 1e+152, + 1e+153, 1e+154, 1e+155, 1e+156, 1e+157, 1e+158, 1e+159, 1e+160, 1e+161, + 1e+162, 1e+163, 1e+164, 1e+165, 1e+166, 1e+167, 1e+168, 1e+169, 1e+170, + 1e+171, 1e+172, 1e+173, 1e+174, 1e+175, 1e+176, 1e+177, 1e+178, 1e+179, + 1e+180, 1e+181, 1e+182, 1e+183, 1e+184, 1e+185, 1e+186, 1e+187, 1e+188, + 1e+189, 1e+190, 1e+191, 1e+192, 1e+193, 1e+194, 1e+195, 1e+196, 1e+197, + 1e+198, 1e+199, 1e+200, 1e+201, 1e+202, 1e+203, 1e+204, 1e+205, 1e+206, + 1e+207, 1e+208, 1e+209, 1e+210, 1e+211, 1e+212, 1e+213, 1e+214, 1e+215, + 1e+216, 1e+217, 1e+218, 1e+219, 1e+220, 1e+221, 1e+222, 1e+223, 1e+224, + 1e+225, 1e+226, 1e+227, 1e+228, 1e+229, 1e+230, 1e+231, 1e+232, 1e+233, + 1e+234, 1e+235, 1e+236, 1e+237, 1e+238, 1e+239, 1e+240, 1e+241, 1e+242, + 1e+243, 1e+244, 1e+245, 1e+246, 1e+247, 1e+248, 1e+249, 1e+250, 1e+251, + 1e+252, 1e+253, 1e+254, 1e+255, 1e+256, 1e+257, 1e+258, 1e+259, 1e+260, + 1e+261, 1e+262, 1e+263, 1e+264, 1e+265, 1e+266, 1e+267, 1e+268, 1e+269, + 1e+270, 1e+271, 1e+272, 1e+273, 1e+274, 1e+275, 1e+276, 1e+277, 1e+278, + 1e+279, 1e+280, 1e+281, 1e+282, 1e+283, 1e+284, 1e+285, 1e+286, 1e+287, + 1e+288, 1e+289, 1e+290, 1e+291, 1e+292, 1e+293, 1e+294, 1e+295, 1e+296, + 1e+297, 1e+298, 1e+299, 1e+300, 1e+301, 1e+302, 1e+303, 1e+304, 1e+305, + 1e+306, 1e+307, 1e+308, +}; + +} // namespace + +double Pow10(int exp) { + if (exp < -324) { + return 0.0; + } else if (exp > 308) { + return INFINITY; + } else { + return kPowersOfTen[exp + 324]; + } +} + +} // namespace strings_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/pow10_helper.h b/absl/strings/internal/pow10_helper.h new file mode 100644 index 00000000..750051bd --- /dev/null +++ b/absl/strings/internal/pow10_helper.h @@ -0,0 +1,38 @@ +// +// Copyright 2018 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This test helper library contains a table of powers of 10, to guarantee +// precise values are computed across the full range of doubles. We can't rely +// on the pow() function, because not all standard libraries ship a version +// that is precise. +#ifndef ABSL_STRINGS_POW10_HELPER_H_ +#define ABSL_STRINGS_POW10_HELPER_H_ + +#include <vector> + +namespace absl { +inline namespace lts_2018_12_18 { +namespace strings_internal { + +// Computes the precise value of 10^exp. (I.e. the nearest representable +// double to the exact value, rounding to nearest-even in the (single) case of +// being exactly halfway between.) +double Pow10(int exp); + +} // namespace strings_internal +} // inline namespace lts_2018_12_18 +} // namespace absl + +#endif // ABSL_STRINGS_POW10_HELPER_H_ diff --git a/absl/strings/internal/pow10_helper_test.cc b/absl/strings/internal/pow10_helper_test.cc new file mode 100644 index 00000000..371fe122 --- /dev/null +++ b/absl/strings/internal/pow10_helper_test.cc @@ -0,0 +1,122 @@ +// Copyright 2018 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/pow10_helper.h" + +#include <cmath> + +#include "gtest/gtest.h" +#include "absl/strings/str_format.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace strings_internal { + +namespace { + +struct TestCase { + int power; // Testing Pow10(power) + uint64_t significand; // Raw bits of the expected value + int radix; // significand is adjusted by 2^radix +}; + +TEST(Pow10HelperTest, Works) { + // The logic in pow10_helper.cc is so simple that theoretically we don't even + // need a test. However, we're paranoid and believe that there may be + // compilers that don't round floating-point literals correctly, even though + // it is specified by the standard. We check various edge cases, just to be + // sure. + constexpr TestCase kTestCases[] = { + // Subnormals + {-323, 0x2, -1074}, + {-322, 0x14, -1074}, + {-321, 0xca, -1074}, + {-320, 0x7e8, -1074}, + {-319, 0x4f10, -1074}, + {-318, 0x316a2, -1074}, + {-317, 0x1ee257, -1074}, + {-316, 0x134d761, -1074}, + {-315, 0xc1069cd, -1074}, + {-314, 0x78a42205, -1074}, + {-313, 0x4b6695433, -1074}, + {-312, 0x2f201d49fb, -1074}, + {-311, 0x1d74124e3d1, -1074}, + {-310, 0x12688b70e62b, -1074}, + {-309, 0xb8157268fdaf, -1074}, + {-308, 0x730d67819e8d2, -1074}, + // Values that are very close to rounding the other way. + // Comment shows difference of significand from the true value. + {-307, 0x11fa182c40c60d, -1072}, // -.4588 + {-290, 0x18f2b061aea072, -1016}, // .4854 + {-276, 0x11BA03F5B21000, -969}, // .4709 + {-259, 0x1899C2F6732210, -913}, // .4830 + {-252, 0x1D53844EE47DD1, -890}, // -.4743 + {-227, 0x1E5297287C2F45, -807}, // -.4708 + {-198, 0x1322E220A5B17E, -710}, // -.4714 + {-195, 0x12B010D3E1CF56, -700}, // .4928 + {-192, 0x123FF06EEA847A, -690}, // .4968 + {-163, 0x1708D0F84D3DE7, -594}, // -.4977 + {-145, 0x13FAAC3E3FA1F3, -534}, // -.4785 + {-111, 0x133D4032C2C7F5, -421}, // .4774 + {-106, 0x1D5B561574765B, -405}, // -.4869 + {-104, 0x16EF5B40C2FC77, -398}, // -.4741 + {-88, 0x197683DF2F268D, -345}, // -.4738 + {-86, 0x13E497065CD61F, -338}, // .4736 + {-76, 0x17288E1271F513, -305}, // -.4761 + {-63, 0x1A53FC9631D10D, -262}, // .4929 + {-30, 0x14484BFEEBC2A0, -152}, // .4758 + {-21, 0x12E3B40A0E9B4F, -122}, // -.4916 + {-5, 0x14F8B588E368F1, -69}, // .4829 + {23, 0x152D02C7E14AF6, 24}, // -.5000 (exactly, round-to-even) + {29, 0x1431E0FAE6D721, 44}, // -.4870 + {34, 0x1ED09BEAD87C03, 60}, // -.4721 + {70, 0x172EBAD6DDC73D, 180}, // .4733 + {105, 0x1BE7ABD3781ECA, 296}, // -.4850 + {126, 0x17A2ECC414A03F, 366}, // -.4999 + {130, 0x1CDA62055B2D9E, 379}, // .4855 + {165, 0x115D847AD00087, 496}, // -.4913 + {172, 0x14B378469B6732, 519}, // .4818 + {187, 0x1262DFEEBBB0F9, 569}, // -.4805 + {210, 0x18557F31326BBB, 645}, // -.4992 + {212, 0x1302CB5E6F642A, 652}, // -.4838 + {215, 0x1290BA9A38C7D1, 662}, // -.4881 + {236, 0x1F736F9B3494E9, 731}, // .4707 + {244, 0x176EC98994F489, 758}, // .4924 + {250, 0x1658E3AB795204, 778}, // -.4963 + {252, 0x117571DDF6C814, 785}, // .4873 + {254, 0x1B4781EAD1989E, 791}, // -.4887 + {260, 0x1A03FDE214CAF1, 811}, // .4784 + {284, 0x1585041B2C477F, 891}, // .4798 + {304, 0x1D2A1BE4048F90, 957}, // -.4987 + // Out-of-range values + {-324, 0x0, 0}, + {-325, 0x0, 0}, + {-326, 0x0, 0}, + {309, 1, 2000}, + {310, 1, 2000}, + {311, 1, 2000}, + }; + for (const TestCase& test_case : kTestCases) { + EXPECT_EQ(Pow10(test_case.power), + std::ldexp(test_case.significand, test_case.radix)) + << absl::StrFormat("Failure for Pow10(%d): %a vs %a", test_case.power, + Pow10(test_case.power), + std::ldexp(test_case.significand, test_case.radix)); + } +} + +} // namespace +} // namespace strings_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/resize_uninitialized.h b/absl/strings/internal/resize_uninitialized.h index b3690332..2951bf84 100644 --- a/absl/strings/internal/resize_uninitialized.h +++ b/absl/strings/internal/resize_uninitialized.h @@ -24,7 +24,7 @@ #include "absl/meta/type_traits.h" // for void_t namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { // Is a subclass of true_type or false_type, depending on whether or not @@ -45,8 +45,8 @@ void ResizeUninit(string_type* s, size_t new_size, std::false_type) { s->resize(new_size); } -// Returns true if the std::string implementation supports a resize where -// the new characters added to the std::string are left untouched. +// Returns true if the string implementation supports a resize where +// the new characters added to the string are left untouched. // // (A better name might be "STLStringSupportsUninitializedResize", alluding to // the previous function.) @@ -58,14 +58,14 @@ inline constexpr bool STLStringSupportsNontrashingResize(string_type*) { // Like str->resize(new_size), except any new characters added to "*str" as a // result of resizing may be left uninitialized, rather than being filled with // '0' bytes. Typically used when code is then going to overwrite the backing -// store of the std::string with known data. Uses a Google extension to std::string. +// store of the string with known data. Uses a Google extension to ::string. template <typename string_type, typename = void> inline void STLStringResizeUninitialized(string_type* s, size_t new_size) { ResizeUninit(s, new_size, HasResizeUninitialized<string_type>()); } } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_RESIZE_UNINITIALIZED_H_ diff --git a/absl/strings/internal/stl_type_traits.h b/absl/strings/internal/stl_type_traits.h index 7fc56a3c..fed7bf7c 100644 --- a/absl/strings/internal/stl_type_traits.h +++ b/absl/strings/internal/stl_type_traits.h @@ -40,7 +40,7 @@ #include "absl/meta/type_traits.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { template <typename C, template <typename...> class T> @@ -243,6 +243,6 @@ struct IsStrictlyBaseOfAndConvertibleToSTLContainer IsConvertibleToSTLContainer<C>> {}; } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_STL_TYPE_TRAITS_H_ diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc new file mode 100644 index 00000000..e5e1eee5 --- /dev/null +++ b/absl/strings/internal/str_format/arg.cc @@ -0,0 +1,379 @@ +// +// POSIX spec: +// http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html +// +#include "absl/strings/internal/str_format/arg.h" + +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include <string> +#include <type_traits> + +#include "absl/base/port.h" +#include "absl/strings/internal/str_format/float_conversion.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { +namespace { + +const char kDigit[2][32] = { "0123456789abcdef", "0123456789ABCDEF" }; + +// Reduce *capacity by s.size(), clipped to a 0 minimum. +void ReducePadding(string_view s, size_t *capacity) { + *capacity = Excess(s.size(), *capacity); +} + +// Reduce *capacity by n, clipped to a 0 minimum. +void ReducePadding(size_t n, size_t *capacity) { + *capacity = Excess(n, *capacity); +} + +template <typename T> +struct MakeUnsigned : std::make_unsigned<T> {}; +template <> +struct MakeUnsigned<absl::uint128> { + using type = absl::uint128; +}; + +template <typename T> +struct IsSigned : std::is_signed<T> {}; +template <> +struct IsSigned<absl::uint128> : std::false_type {}; + +class ConvertedIntInfo { + public: + template <typename T> + ConvertedIntInfo(T v, ConversionChar conv) { + using Unsigned = typename MakeUnsigned<T>::type; + auto u = static_cast<Unsigned>(v); + if (IsNeg(v)) { + is_neg_ = true; + u = Unsigned{} - u; + } else { + is_neg_ = false; + } + UnsignedToStringRight(u, conv); + } + + string_view digits() const { + return {end() - size_, static_cast<size_t>(size_)}; + } + bool is_neg() const { return is_neg_; } + + private: + template <typename T, bool IsSigned> + struct IsNegImpl { + static bool Eval(T v) { return v < 0; } + }; + template <typename T> + struct IsNegImpl<T, false> { + static bool Eval(T) { + return false; + } + }; + + template <typename T> + bool IsNeg(T v) { + return IsNegImpl<T, IsSigned<T>::value>::Eval(v); + } + + template <typename T> + void UnsignedToStringRight(T u, ConversionChar conv) { + char *p = end(); + switch (conv.radix()) { + default: + case 10: + for (; u; u /= 10) + *--p = static_cast<char>('0' + static_cast<size_t>(u % 10)); + break; + case 8: + for (; u; u /= 8) + *--p = static_cast<char>('0' + static_cast<size_t>(u % 8)); + break; + case 16: { + const char *digits = kDigit[conv.upper() ? 1 : 0]; + for (; u; u /= 16) *--p = digits[static_cast<size_t>(u % 16)]; + break; + } + } + size_ = static_cast<int>(end() - p); + } + + const char *end() const { return storage_ + sizeof(storage_); } + char *end() { return storage_ + sizeof(storage_); } + + bool is_neg_; + int size_; + // Max size: 128 bit value as octal -> 43 digits + char storage_[128 / 3 + 1]; +}; + +// Note: 'o' conversions do not have a base indicator, it's just that +// the '#' flag is specified to modify the precision for 'o' conversions. +string_view BaseIndicator(const ConvertedIntInfo &info, + const ConversionSpec conv) { + bool alt = conv.flags().alt; + int radix = conv.conv().radix(); + if (conv.conv().id() == ConversionChar::p) + alt = true; // always show 0x for %p. + // From the POSIX description of '#' flag: + // "For x or X conversion specifiers, a non-zero result shall have + // 0x (or 0X) prefixed to it." + if (alt && radix == 16 && !info.digits().empty()) { + if (conv.conv().upper()) return "0X"; + return "0x"; + } + return {}; +} + +string_view SignColumn(bool neg, const ConversionSpec conv) { + if (conv.conv().is_signed()) { + if (neg) return "-"; + if (conv.flags().show_pos) return "+"; + if (conv.flags().sign_col) return " "; + } + return {}; +} + +bool ConvertCharImpl(unsigned char v, const ConversionSpec conv, + FormatSinkImpl *sink) { + size_t fill = 0; + if (conv.width() >= 0) fill = conv.width(); + ReducePadding(1, &fill); + if (!conv.flags().left) sink->Append(fill, ' '); + sink->Append(1, v); + if (conv.flags().left) sink->Append(fill, ' '); + return true; +} + +bool ConvertIntImplInner(const ConvertedIntInfo &info, + const ConversionSpec conv, FormatSinkImpl *sink) { + // Print as a sequence of Substrings: + // [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces] + size_t fill = 0; + if (conv.width() >= 0) fill = conv.width(); + + string_view formatted = info.digits(); + ReducePadding(formatted, &fill); + + string_view sign = SignColumn(info.is_neg(), conv); + ReducePadding(sign, &fill); + + string_view base_indicator = BaseIndicator(info, conv); + ReducePadding(base_indicator, &fill); + + int precision = conv.precision(); + bool precision_specified = precision >= 0; + if (!precision_specified) + precision = 1; + + if (conv.flags().alt && conv.conv().id() == ConversionChar::o) { + // From POSIX description of the '#' (alt) flag: + // "For o conversion, it increases the precision (if necessary) to + // force the first digit of the result to be zero." + if (formatted.empty() || *formatted.begin() != '0') { + int needed = static_cast<int>(formatted.size()) + 1; + precision = std::max(precision, needed); + } + } + + size_t num_zeroes = Excess(formatted.size(), precision); + ReducePadding(num_zeroes, &fill); + + size_t num_left_spaces = !conv.flags().left ? fill : 0; + size_t num_right_spaces = conv.flags().left ? fill : 0; + + // From POSIX description of the '0' (zero) flag: + // "For d, i, o, u, x, and X conversion specifiers, if a precision + // is specified, the '0' flag is ignored." + if (!precision_specified && conv.flags().zero) { + num_zeroes += num_left_spaces; + num_left_spaces = 0; + } + + sink->Append(num_left_spaces, ' '); + sink->Append(sign); + sink->Append(base_indicator); + sink->Append(num_zeroes, '0'); + sink->Append(formatted); + sink->Append(num_right_spaces, ' '); + return true; +} + +template <typename T> +bool ConvertIntImplInner(T v, const ConversionSpec conv, FormatSinkImpl *sink) { + ConvertedIntInfo info(v, conv.conv()); + if (conv.flags().basic && conv.conv().id() != ConversionChar::p) { + if (info.is_neg()) sink->Append(1, '-'); + if (info.digits().empty()) { + sink->Append(1, '0'); + } else { + sink->Append(info.digits()); + } + return true; + } + return ConvertIntImplInner(info, conv, sink); +} + +template <typename T> +bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) { + if (conv.conv().is_float()) { + return FormatConvertImpl(static_cast<double>(v), conv, sink).value; + } + if (conv.conv().id() == ConversionChar::c) + return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink); + if (!conv.conv().is_integral()) + return false; + if (!conv.conv().is_signed() && IsSigned<T>::value) { + using U = typename MakeUnsigned<T>::type; + return FormatConvertImpl(static_cast<U>(v), conv, sink).value; + } + return ConvertIntImplInner(v, conv, sink); +} + +template <typename T> +bool ConvertFloatArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) { + return conv.conv().is_float() && ConvertFloatImpl(v, conv, sink); +} + +inline bool ConvertStringArg(string_view v, const ConversionSpec conv, + FormatSinkImpl *sink) { + if (conv.conv().id() != ConversionChar::s) + return false; + if (conv.flags().basic) { + sink->Append(v); + return true; + } + return sink->PutPaddedString(v, conv.width(), conv.precision(), + conv.flags().left); +} + +} // namespace + +// ==================== Strings ==================== +ConvertResult<Conv::s> FormatConvertImpl(const std::string &v, + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertStringArg(v, conv, sink)}; +} + +ConvertResult<Conv::s> FormatConvertImpl(string_view v, + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertStringArg(v, conv, sink)}; +} + +ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char *v, + const ConversionSpec conv, + FormatSinkImpl *sink) { + if (conv.conv().id() == ConversionChar::p) + return {FormatConvertImpl(VoidPtr(v), conv, sink).value}; + size_t len; + if (v == nullptr) { + len = 0; + } else if (conv.precision() < 0) { + len = std::strlen(v); + } else { + // If precision is set, we look for the null terminator on the valid range. + len = std::find(v, v + conv.precision(), '\0') - v; + } + return {ConvertStringArg(string_view(v, len), conv, sink)}; +} + +// ==================== Raw pointers ==================== +ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec conv, + FormatSinkImpl *sink) { + if (conv.conv().id() != ConversionChar::p) + return {false}; + if (!v.value) { + sink->Append("(nil)"); + return {true}; + } + return {ConvertIntImplInner(v.value, conv, sink)}; +} + +// ==================== Floats ==================== +FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertFloatArg(v, conv, sink)}; +} +FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertFloatArg(v, conv, sink)}; +} +FloatingConvertResult FormatConvertImpl(long double v, + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertFloatArg(v, conv, sink)}; +} + +// ==================== Chars ==================== +IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(signed char v, + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(unsigned char v, + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} + +// ==================== Ints ==================== +IntegralConvertResult FormatConvertImpl(short v, // NOLINT + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(long v, // NOLINT + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(long long v, // NOLINT + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(absl::uint128 v, + const ConversionSpec conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} + +ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(); + + +} // namespace str_format_internal + +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h new file mode 100644 index 00000000..0af4c839 --- /dev/null +++ b/absl/strings/internal/str_format/arg.h @@ -0,0 +1,422 @@ +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_ + +#include <string.h> +#include <wchar.h> + +#include <cstdio> +#include <iomanip> +#include <limits> +#include <sstream> +#include <string> +#include <type_traits> + +#include "absl/base/port.h" +#include "absl/meta/type_traits.h" +#include "absl/numeric/int128.h" +#include "absl/strings/internal/str_format/extension.h" +#include "absl/strings/string_view.h" + +class Cord; +class CordReader; + +namespace absl { +inline namespace lts_2018_12_18 { + +class FormatCountCapture; +class FormatSink; + +namespace str_format_internal { + +template <typename T, typename = void> +struct HasUserDefinedConvert : std::false_type {}; + +template <typename T> +struct HasUserDefinedConvert< + T, void_t<decltype(AbslFormatConvert( + std::declval<const T&>(), std::declval<ConversionSpec>(), + std::declval<FormatSink*>()))>> : std::true_type {}; +template <typename T> +class StreamedWrapper; + +// If 'v' can be converted (in the printf sense) according to 'conv', +// then convert it, appending to `sink` and return `true`. +// Otherwise fail and return `false`. +// Raw pointers. +struct VoidPtr { + VoidPtr() = default; + template <typename T, + decltype(reinterpret_cast<uintptr_t>(std::declval<T*>())) = 0> + VoidPtr(T* ptr) // NOLINT + : value(ptr ? reinterpret_cast<uintptr_t>(ptr) : 0) {} + uintptr_t value; +}; +ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, ConversionSpec conv, + FormatSinkImpl* sink); + +// Strings. +ConvertResult<Conv::s> FormatConvertImpl(const std::string& v, ConversionSpec conv, + FormatSinkImpl* sink); +ConvertResult<Conv::s> FormatConvertImpl(string_view v, ConversionSpec conv, + FormatSinkImpl* sink); +ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char* v, + ConversionSpec conv, + FormatSinkImpl* sink); +template <class AbslCord, + typename std::enable_if< + std::is_same<AbslCord, ::Cord>::value>::type* = nullptr, + class AbslCordReader = ::CordReader> +ConvertResult<Conv::s> FormatConvertImpl(const AbslCord& value, + ConversionSpec conv, + FormatSinkImpl* sink) { + if (conv.conv().id() != ConversionChar::s) return {false}; + + bool is_left = conv.flags().left; + size_t space_remaining = 0; + + int width = conv.width(); + if (width >= 0) space_remaining = width; + + size_t to_write = value.size(); + + int precision = conv.precision(); + if (precision >= 0) + to_write = std::min(to_write, static_cast<size_t>(precision)); + + space_remaining = Excess(to_write, space_remaining); + + if (space_remaining > 0 && !is_left) sink->Append(space_remaining, ' '); + + string_view piece; + for (AbslCordReader reader(value); + to_write > 0 && reader.ReadFragment(&piece); to_write -= piece.size()) { + if (piece.size() > to_write) piece.remove_suffix(piece.size() - to_write); + sink->Append(piece); + } + + if (space_remaining > 0 && is_left) sink->Append(space_remaining, ' '); + return {true}; +} + +using IntegralConvertResult = + ConvertResult<Conv::c | Conv::numeric | Conv::star>; +using FloatingConvertResult = ConvertResult<Conv::floating>; + +// Floats. +FloatingConvertResult FormatConvertImpl(float v, ConversionSpec conv, + FormatSinkImpl* sink); +FloatingConvertResult FormatConvertImpl(double v, ConversionSpec conv, + FormatSinkImpl* sink); +FloatingConvertResult FormatConvertImpl(long double v, ConversionSpec conv, + FormatSinkImpl* sink); + +// Chars. +IntegralConvertResult FormatConvertImpl(char v, ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(signed char v, ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(unsigned char v, ConversionSpec conv, + FormatSinkImpl* sink); + +// Ints. +IntegralConvertResult FormatConvertImpl(short v, // NOLINT + ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT + ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(int v, ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(unsigned v, ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(long v, // NOLINT + ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT + ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(long long v, // NOLINT + ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT + ConversionSpec conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(uint128 v, ConversionSpec conv, + FormatSinkImpl* sink); +template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0> +IntegralConvertResult FormatConvertImpl(T v, ConversionSpec conv, + FormatSinkImpl* sink) { + return FormatConvertImpl(static_cast<int>(v), conv, sink); +} + +// We provide this function to help the checker, but it is never defined. +// FormatArgImpl will use the underlying Convert functions instead. +template <typename T> +typename std::enable_if<std::is_enum<T>::value && + !HasUserDefinedConvert<T>::value, + IntegralConvertResult>::type +FormatConvertImpl(T v, ConversionSpec conv, FormatSinkImpl* sink); + +template <typename T> +ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<T>& v, + ConversionSpec conv, + FormatSinkImpl* out) { + std::ostringstream oss; + oss << v.v_; + if (!oss) return {false}; + return str_format_internal::FormatConvertImpl(oss.str(), conv, out); +} + +// Use templates and dependent types to delay evaluation of the function +// until after FormatCountCapture is fully defined. +struct FormatCountCaptureHelper { + template <class T = int> + static ConvertResult<Conv::n> ConvertHelper(const FormatCountCapture& v, + ConversionSpec conv, + FormatSinkImpl* sink) { + const absl::enable_if_t<sizeof(T) != 0, FormatCountCapture>& v2 = v; + + if (conv.conv().id() != str_format_internal::ConversionChar::n) + return {false}; + *v2.p_ = static_cast<int>(sink->size()); + return {true}; + } +}; + +template <class T = int> +ConvertResult<Conv::n> FormatConvertImpl(const FormatCountCapture& v, + ConversionSpec conv, + FormatSinkImpl* sink) { + return FormatCountCaptureHelper::ConvertHelper(v, conv, sink); +} + +// Helper friend struct to hide implementation details from the public API of +// FormatArgImpl. +struct FormatArgImplFriend { + template <typename Arg> + static bool ToInt(Arg arg, int* out) { + // A value initialized ConversionSpec has a `none` conv, which tells the + // dispatcher to run the `int` conversion. + return arg.dispatcher_(arg.data_, {}, out); + } + + template <typename Arg> + static bool Convert(Arg arg, str_format_internal::ConversionSpec conv, + FormatSinkImpl* out) { + return arg.dispatcher_(arg.data_, conv, out); + } + + template <typename Arg> + static typename Arg::Dispatcher GetVTablePtrForTest(Arg arg) { + return arg.dispatcher_; + } +}; + +// A type-erased handle to a format argument. +class FormatArgImpl { + private: + enum { kInlinedSpace = 8 }; + + using VoidPtr = str_format_internal::VoidPtr; + + union Data { + const void* ptr; + const volatile void* volatile_ptr; + char buf[kInlinedSpace]; + }; + + using Dispatcher = bool (*)(Data, ConversionSpec, void* out); + + template <typename T> + struct store_by_value + : std::integral_constant<bool, (sizeof(T) <= kInlinedSpace) && + (std::is_integral<T>::value || + std::is_floating_point<T>::value || + std::is_pointer<T>::value || + std::is_same<VoidPtr, T>::value)> {}; + + enum StoragePolicy { ByPointer, ByVolatilePointer, ByValue }; + template <typename T> + struct storage_policy + : std::integral_constant<StoragePolicy, + (std::is_volatile<T>::value + ? ByVolatilePointer + : (store_by_value<T>::value ? ByValue + : ByPointer))> { + }; + + // To reduce the number of vtables we will decay values before hand. + // Anything with a user-defined Convert will get its own vtable. + // For everything else: + // - Decay char* and char arrays into `const char*` + // - Decay any other pointer to `const void*` + // - Decay all enums to their underlying type. + // - Decay function pointers to void*. + template <typename T, typename = void> + struct DecayType { + static constexpr bool kHasUserDefined = + str_format_internal::HasUserDefinedConvert<T>::value; + using type = typename std::conditional< + !kHasUserDefined && std::is_convertible<T, const char*>::value, + const char*, + typename std::conditional<!kHasUserDefined && + std::is_convertible<T, VoidPtr>::value, + VoidPtr, const T&>::type>::type; + }; + template <typename T> + struct DecayType<T, + typename std::enable_if< + !str_format_internal::HasUserDefinedConvert<T>::value && + std::is_enum<T>::value>::type> { + using type = typename std::underlying_type<T>::type; + }; + + public: + template <typename T> + explicit FormatArgImpl(const T& value) { + using D = typename DecayType<T>::type; + static_assert( + std::is_same<D, const T&>::value || storage_policy<D>::value == ByValue, + "Decayed types must be stored by value"); + Init(static_cast<D>(value)); + } + + private: + friend struct str_format_internal::FormatArgImplFriend; + template <typename T, StoragePolicy = storage_policy<T>::value> + struct Manager; + + template <typename T> + struct Manager<T, ByPointer> { + static Data SetValue(const T& value) { + Data data; + data.ptr = &value; + return data; + } + + static const T& Value(Data arg) { return *static_cast<const T*>(arg.ptr); } + }; + + template <typename T> + struct Manager<T, ByVolatilePointer> { + static Data SetValue(const T& value) { + Data data; + data.volatile_ptr = &value; + return data; + } + + static const T& Value(Data arg) { + return *static_cast<const T*>(arg.volatile_ptr); + } + }; + + template <typename T> + struct Manager<T, ByValue> { + static Data SetValue(const T& value) { + Data data; + memcpy(data.buf, &value, sizeof(value)); + return data; + } + + static T Value(Data arg) { + T value; + memcpy(&value, arg.buf, sizeof(T)); + return value; + } + }; + + template <typename T> + void Init(const T& value) { + data_ = Manager<T>::SetValue(value); + dispatcher_ = &Dispatch<T>; + } + + template <typename T> + static int ToIntVal(const T& val) { + using CommonType = typename std::conditional<std::is_signed<T>::value, + int64_t, uint64_t>::type; + if (static_cast<CommonType>(val) > + static_cast<CommonType>((std::numeric_limits<int>::max)())) { + return (std::numeric_limits<int>::max)(); + } else if (std::is_signed<T>::value && + static_cast<CommonType>(val) < + static_cast<CommonType>((std::numeric_limits<int>::min)())) { + return (std::numeric_limits<int>::min)(); + } + return static_cast<int>(val); + } + + template <typename T> + static bool ToInt(Data arg, int* out, std::true_type /* is_integral */, + std::false_type) { + *out = ToIntVal(Manager<T>::Value(arg)); + return true; + } + + template <typename T> + static bool ToInt(Data arg, int* out, std::false_type, + std::true_type /* is_enum */) { + *out = ToIntVal(static_cast<typename std::underlying_type<T>::type>( + Manager<T>::Value(arg))); + return true; + } + + template <typename T> + static bool ToInt(Data, int*, std::false_type, std::false_type) { + return false; + } + + template <typename T> + static bool Dispatch(Data arg, ConversionSpec spec, void* out) { + // A `none` conv indicates that we want the `int` conversion. + if (ABSL_PREDICT_FALSE(spec.conv().id() == ConversionChar::none)) { + return ToInt<T>(arg, static_cast<int*>(out), std::is_integral<T>(), + std::is_enum<T>()); + } + + return str_format_internal::FormatConvertImpl( + Manager<T>::Value(arg), spec, static_cast<FormatSinkImpl*>(out)) + .value; + } + + Data data_; + Dispatcher dispatcher_; +}; + +#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \ + E template bool FormatArgImpl::Dispatch<T>(Data, ConversionSpec, void*) + +#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \ + __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(bool, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(char, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(signed char, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned char, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(short, __VA_ARGS__); /* NOLINT */ \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned short, /* NOLINT */ \ + __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(int, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned int, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long, __VA_ARGS__); /* NOLINT */ \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned long, /* NOLINT */ \ + __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long long, /* NOLINT */ \ + __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned long long, /* NOLINT */ \ + __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(uint128, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(float, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(double, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long double, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const char*, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::string, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(string_view, __VA_ARGS__) + +ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(extern); + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_ diff --git a/absl/strings/internal/str_format/arg_test.cc b/absl/strings/internal/str_format/arg_test.cc new file mode 100644 index 00000000..9cb9559c --- /dev/null +++ b/absl/strings/internal/str_format/arg_test.cc @@ -0,0 +1,113 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +#include "absl/strings/internal/str_format/arg.h" + +#include <ostream> +#include <string> +#include "gtest/gtest.h" +#include "absl/strings/str_format.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { +namespace { + +class FormatArgImplTest : public ::testing::Test { + public: + enum Color { kRed, kGreen, kBlue }; + + static const char *hi() { return "hi"; } +}; + +TEST_F(FormatArgImplTest, ToInt) { + int out = 0; + EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(1), &out)); + EXPECT_EQ(1, out); + EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(-1), &out)); + EXPECT_EQ(-1, out); + EXPECT_TRUE( + FormatArgImplFriend::ToInt(FormatArgImpl(static_cast<char>(64)), &out)); + EXPECT_EQ(64, out); + EXPECT_TRUE(FormatArgImplFriend::ToInt( + FormatArgImpl(static_cast<unsigned long long>(123456)), &out)); // NOLINT + EXPECT_EQ(123456, out); + EXPECT_TRUE(FormatArgImplFriend::ToInt( + FormatArgImpl(static_cast<unsigned long long>( // NOLINT + std::numeric_limits<int>::max()) + + 1), + &out)); + EXPECT_EQ(std::numeric_limits<int>::max(), out); + EXPECT_TRUE(FormatArgImplFriend::ToInt( + FormatArgImpl(static_cast<long long>( // NOLINT + std::numeric_limits<int>::min()) - + 10), + &out)); + EXPECT_EQ(std::numeric_limits<int>::min(), out); + EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(false), &out)); + EXPECT_EQ(0, out); + EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(true), &out)); + EXPECT_EQ(1, out); + EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(2.2), &out)); + EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(3.2f), &out)); + EXPECT_FALSE(FormatArgImplFriend::ToInt( + FormatArgImpl(static_cast<int *>(nullptr)), &out)); + EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(hi()), &out)); + EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl("hi"), &out)); + EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(kBlue), &out)); + EXPECT_EQ(2, out); +} + +extern const char kMyArray[]; + +TEST_F(FormatArgImplTest, CharArraysDecayToCharPtr) { + const char* a = ""; + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(""))); + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl("A"))); + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl("ABC"))); + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyArray))); +} + +TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) { + auto expected = FormatArgImplFriend::GetVTablePtrForTest( + FormatArgImpl(static_cast<void *>(nullptr))); + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest( + FormatArgImpl(static_cast<int *>(nullptr))), + expected); + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest( + FormatArgImpl(static_cast<volatile int *>(nullptr))), + expected); + + auto p = static_cast<void (*)()>([] {}); + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(p)), + expected); +} + +TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) { + std::string s; + FormatSinkImpl sink(&s); + ConversionSpec conv; + conv.set_conv(ConversionChar::FromChar('s')); + conv.set_flags(Flags()); + conv.set_width(-1); + conv.set_precision(-1); + EXPECT_TRUE( + FormatArgImplFriend::Convert(FormatArgImpl(kMyArray), conv, &sink)); + sink.Flush(); + EXPECT_EQ("ABCDE", s); +} +const char kMyArray[] = "ABCDE"; + +} // namespace +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/bind.cc b/absl/strings/internal/str_format/bind.cc new file mode 100644 index 00000000..5cf026b6 --- /dev/null +++ b/absl/strings/internal/str_format/bind.cc @@ -0,0 +1,231 @@ +#include "absl/strings/internal/str_format/bind.h" + +#include <cerrno> +#include <limits> +#include <sstream> +#include <string> + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { + +namespace { + +inline bool BindFromPosition(int position, int* value, + absl::Span<const FormatArgImpl> pack) { + assert(position > 0); + if (static_cast<size_t>(position) > pack.size()) { + return false; + } + // -1 because positions are 1-based + return FormatArgImplFriend::ToInt(pack[position - 1], value); +} + +class ArgContext { + public: + explicit ArgContext(absl::Span<const FormatArgImpl> pack) : pack_(pack) {} + + // Fill 'bound' with the results of applying the context's argument pack + // to the specified 'props'. We synthesize a BoundConversion by + // lining up a UnboundConversion with a user argument. We also + // resolve any '*' specifiers for width and precision, so after + // this call, 'bound' has all the information it needs to be formatted. + // Returns false on failure. + bool Bind(const UnboundConversion *props, BoundConversion *bound); + + private: + absl::Span<const FormatArgImpl> pack_; +}; + +inline bool ArgContext::Bind(const UnboundConversion* unbound, + BoundConversion* bound) { + const FormatArgImpl* arg = nullptr; + int arg_position = unbound->arg_position; + if (static_cast<size_t>(arg_position - 1) >= pack_.size()) return false; + arg = &pack_[arg_position - 1]; // 1-based + + if (!unbound->flags.basic) { + int width = unbound->width.value(); + bool force_left = false; + if (unbound->width.is_from_arg()) { + if (!BindFromPosition(unbound->width.get_from_arg(), &width, pack_)) + return false; + if (width < 0) { + // "A negative field width is taken as a '-' flag followed by a + // positive field width." + force_left = true; + width = -width; + } + } + + int precision = unbound->precision.value(); + if (unbound->precision.is_from_arg()) { + if (!BindFromPosition(unbound->precision.get_from_arg(), &precision, + pack_)) + return false; + } + + bound->set_width(width); + bound->set_precision(precision); + bound->set_flags(unbound->flags); + if (force_left) + bound->set_left(true); + } else { + bound->set_flags(unbound->flags); + bound->set_width(-1); + bound->set_precision(-1); + } + + bound->set_length_mod(unbound->length_mod); + bound->set_conv(unbound->conv); + bound->set_arg(arg); + return true; +} + +template <typename Converter> +class ConverterConsumer { + public: + ConverterConsumer(Converter converter, absl::Span<const FormatArgImpl> pack) + : converter_(converter), arg_context_(pack) {} + + bool Append(string_view s) { + converter_.Append(s); + return true; + } + bool ConvertOne(const UnboundConversion& conv, string_view conv_string) { + BoundConversion bound; + if (!arg_context_.Bind(&conv, &bound)) return false; + return converter_.ConvertOne(bound, conv_string); + } + + private: + Converter converter_; + ArgContext arg_context_; +}; + +template <typename Converter> +bool ConvertAll(const UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args, Converter converter) { + if (format.has_parsed_conversion()) { + return format.parsed_conversion()->ProcessFormat( + ConverterConsumer<Converter>(converter, args)); + } else { + return ParseFormatString(format.str(), + ConverterConsumer<Converter>(converter, args)); + } +} + +class DefaultConverter { + public: + explicit DefaultConverter(FormatSinkImpl* sink) : sink_(sink) {} + + void Append(string_view s) const { sink_->Append(s); } + + bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const { + return FormatArgImplFriend::Convert(*bound.arg(), bound, sink_); + } + + private: + FormatSinkImpl* sink_; +}; + +class SummarizingConverter { + public: + explicit SummarizingConverter(FormatSinkImpl* sink) : sink_(sink) {} + + void Append(string_view s) const { sink_->Append(s); } + + bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const { + UntypedFormatSpecImpl spec("%d"); + + std::ostringstream ss; + ss << "{" << Streamable(spec, {*bound.arg()}) << ":" << bound.flags(); + if (bound.width() >= 0) ss << bound.width(); + if (bound.precision() >= 0) ss << "." << bound.precision(); + ss << bound.length_mod() << bound.conv() << "}"; + Append(ss.str()); + return true; + } + + private: + FormatSinkImpl* sink_; +}; + +} // namespace + +bool BindWithPack(const UnboundConversion* props, + absl::Span<const FormatArgImpl> pack, + BoundConversion* bound) { + return ArgContext(pack).Bind(props, bound); +} + +std::string Summarize(const UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args) { + typedef SummarizingConverter Converter; + std::string out; + { + // inner block to destroy sink before returning out. It ensures a last + // flush. + FormatSinkImpl sink(&out); + if (!ConvertAll(format, args, Converter(&sink))) { + return ""; + } + } + return out; +} + +bool FormatUntyped(FormatRawSinkImpl raw_sink, + const UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args) { + FormatSinkImpl sink(raw_sink); + using Converter = DefaultConverter; + return ConvertAll(format, args, Converter(&sink)); +} + +std::ostream& Streamable::Print(std::ostream& os) const { + if (!FormatUntyped(&os, format_, args_)) os.setstate(std::ios::failbit); + return os; +} + +std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args) { + size_t orig = out->size(); + if (ABSL_PREDICT_FALSE(!FormatUntyped(out, format, args))) { + out->erase(orig); + } + return *out; +} + +int FprintF(std::FILE* output, const UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args) { + FILERawSink sink(output); + if (!FormatUntyped(&sink, format, args)) { + errno = EINVAL; + return -1; + } + if (sink.error()) { + errno = sink.error(); + return -1; + } + if (sink.count() > std::numeric_limits<int>::max()) { + errno = EFBIG; + return -1; + } + return static_cast<int>(sink.count()); +} + +int SnprintF(char* output, size_t size, const UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args) { + BufferRawSink sink(output, size ? size - 1 : 0); + if (!FormatUntyped(&sink, format, args)) { + errno = EINVAL; + return -1; + } + size_t total = sink.total_written(); + if (size) output[std::min(total, size - 1)] = 0; + return static_cast<int>(total); +} + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h new file mode 100644 index 00000000..df5562f6 --- /dev/null +++ b/absl/strings/internal/str_format/bind.h @@ -0,0 +1,199 @@ +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ + +#include <array> +#include <cstdio> +#include <sstream> +#include <string> + +#include "absl/base/port.h" +#include "absl/container/inlined_vector.h" +#include "absl/strings/internal/str_format/arg.h" +#include "absl/strings/internal/str_format/checker.h" +#include "absl/strings/internal/str_format/parser.h" +#include "absl/types/span.h" + +namespace absl { +inline namespace lts_2018_12_18 { + +class UntypedFormatSpec; + +namespace str_format_internal { + +class BoundConversion : public ConversionSpec { + public: + const FormatArgImpl* arg() const { return arg_; } + void set_arg(const FormatArgImpl* a) { arg_ = a; } + + private: + const FormatArgImpl* arg_; +}; + +// This is the type-erased class that the implementation uses. +class UntypedFormatSpecImpl { + public: + UntypedFormatSpecImpl() = delete; + + explicit UntypedFormatSpecImpl(string_view s) + : data_(s.data()), size_(s.size()) {} + explicit UntypedFormatSpecImpl( + const str_format_internal::ParsedFormatBase* pc) + : data_(pc), size_(~size_t{}) {} + + bool has_parsed_conversion() const { return size_ == ~size_t{}; } + + string_view str() const { + assert(!has_parsed_conversion()); + return string_view(static_cast<const char*>(data_), size_); + } + const str_format_internal::ParsedFormatBase* parsed_conversion() const { + assert(has_parsed_conversion()); + return static_cast<const str_format_internal::ParsedFormatBase*>(data_); + } + + template <typename T> + static const UntypedFormatSpecImpl& Extract(const T& s) { + return s.spec_; + } + + private: + const void* data_; + size_t size_; +}; + +template <typename T, typename...> +struct MakeDependent { + using type = T; +}; + +// Implicitly convertible from `const char*`, `string_view`, and the +// `ExtendedParsedFormat` type. This abstraction allows all format functions to +// operate on any without providing too many overloads. +template <typename... Args> +class FormatSpecTemplate + : public MakeDependent<UntypedFormatSpec, Args...>::type { + using Base = typename MakeDependent<UntypedFormatSpec, Args...>::type; + + public: +#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + + // Honeypot overload for when the std::string is not constexpr. + // We use the 'unavailable' attribute to give a better compiler error than + // just 'method is deleted'. + FormatSpecTemplate(...) // NOLINT + __attribute__((unavailable("Format std::string is not constexpr."))); + + // Honeypot overload for when the format is constexpr and invalid. + // We use the 'unavailable' attribute to give a better compiler error than + // just 'method is deleted'. + // To avoid checking the format twice, we just check that the format is + // constexpr. If is it valid, then the overload below will kick in. + // We add the template here to make this overload have lower priority. + template <typename = void> + FormatSpecTemplate(const char* s) // NOLINT + __attribute__(( + enable_if(str_format_internal::EnsureConstexpr(s), "constexpr trap"), + unavailable( + "Format specified does not match the arguments passed."))); + + template <typename T = void> + FormatSpecTemplate(string_view s) // NOLINT + __attribute__((enable_if(str_format_internal::EnsureConstexpr(s), + "constexpr trap"))) { + static_assert(sizeof(T*) == 0, + "Format specified does not match the arguments passed."); + } + + // Good format overload. + FormatSpecTemplate(const char* s) // NOLINT + __attribute__((enable_if(ValidFormatImpl<ArgumentToConv<Args>()...>(s), + "bad format trap"))) + : Base(s) {} + + FormatSpecTemplate(string_view s) // NOLINT + __attribute__((enable_if(ValidFormatImpl<ArgumentToConv<Args>()...>(s), + "bad format trap"))) + : Base(s) {} + +#else // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + + FormatSpecTemplate(const char* s) : Base(s) {} // NOLINT + FormatSpecTemplate(string_view s) : Base(s) {} // NOLINT + +#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + + template <Conv... C, typename = typename std::enable_if< + sizeof...(C) == sizeof...(Args) && + AllOf(Contains(ArgumentToConv<Args>(), + C)...)>::type> + FormatSpecTemplate(const ExtendedParsedFormat<C...>& pc) // NOLINT + : Base(&pc) {} +}; + +template <typename... Args> +struct FormatSpecDeductionBarrier { + using type = FormatSpecTemplate<Args...>; +}; + +class Streamable { + public: + Streamable(const UntypedFormatSpecImpl& format, + absl::Span<const FormatArgImpl> args) + : format_(format), args_(args.begin(), args.end()) {} + + std::ostream& Print(std::ostream& os) const; + + friend std::ostream& operator<<(std::ostream& os, const Streamable& l) { + return l.Print(os); + } + + private: + const UntypedFormatSpecImpl& format_; + absl::InlinedVector<FormatArgImpl, 4> args_; +}; + +// for testing +std::string Summarize(UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args); +bool BindWithPack(const UnboundConversion* props, + absl::Span<const FormatArgImpl> pack, BoundConversion* bound); + +bool FormatUntyped(FormatRawSinkImpl raw_sink, + UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args); + +std::string& AppendPack(std::string* out, UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args); + +inline std::string FormatPack(const UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args) { + std::string out; + AppendPack(&out, format, args); + return out; +} + +int FprintF(std::FILE* output, UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args); +int SnprintF(char* output, size_t size, UntypedFormatSpecImpl format, + absl::Span<const FormatArgImpl> args); + +// Returned by Streamed(v). Converts via '%s' to the string created +// by std::ostream << v. +template <typename T> +class StreamedWrapper { + public: + explicit StreamedWrapper(const T& v) : v_(v) { } + + private: + template <typename S> + friend ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<S>& v, + ConversionSpec conv, + FormatSinkImpl* out); + const T& v_; +}; + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ diff --git a/absl/strings/internal/str_format/bind_test.cc b/absl/strings/internal/str_format/bind_test.cc new file mode 100644 index 00000000..58d9e072 --- /dev/null +++ b/absl/strings/internal/str_format/bind_test.cc @@ -0,0 +1,133 @@ +#include "absl/strings/internal/str_format/bind.h" + +#include <string.h> + +#include "gtest/gtest.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { +namespace { + +template <typename T, size_t N> +size_t ArraySize(T (&)[N]) { + return N; +} + +class FormatBindTest : public ::testing::Test { + public: + bool Extract(const char *s, UnboundConversion *props, int *next) const { + absl::string_view src = s; + return ConsumeUnboundConversion(&src, props, next) && src.empty(); + } +}; + +TEST_F(FormatBindTest, BindSingle) { + struct Expectation { + int line; + const char *fmt; + int ok_phases; + const FormatArgImpl *arg; + int width; + int precision; + int next_arg; + }; + const int no = -1; + const int ia[] = { 10, 20, 30, 40}; + const FormatArgImpl args[] = {FormatArgImpl(ia[0]), FormatArgImpl(ia[1]), + FormatArgImpl(ia[2]), FormatArgImpl(ia[3])}; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + const Expectation kExpect[] = { + {__LINE__, "d", 2, &args[0], no, no, 2}, + {__LINE__, "4d", 2, &args[0], 4, no, 2}, + {__LINE__, ".5d", 2, &args[0], no, 5, 2}, + {__LINE__, "4.5d", 2, &args[0], 4, 5, 2}, + {__LINE__, "*d", 2, &args[1], 10, no, 3}, + {__LINE__, ".*d", 2, &args[1], no, 10, 3}, + {__LINE__, "*.*d", 2, &args[2], 10, 20, 4}, + {__LINE__, "1$d", 2, &args[0], no, no, 0}, + {__LINE__, "2$d", 2, &args[1], no, no, 0}, + {__LINE__, "3$d", 2, &args[2], no, no, 0}, + {__LINE__, "4$d", 2, &args[3], no, no, 0}, + {__LINE__, "2$*1$d", 2, &args[1], 10, no, 0}, + {__LINE__, "2$*2$d", 2, &args[1], 20, no, 0}, + {__LINE__, "2$*3$d", 2, &args[1], 30, no, 0}, + {__LINE__, "2$.*1$d", 2, &args[1], no, 10, 0}, + {__LINE__, "2$.*2$d", 2, &args[1], no, 20, 0}, + {__LINE__, "2$.*3$d", 2, &args[1], no, 30, 0}, + {__LINE__, "2$*3$.*1$d", 2, &args[1], 30, 10, 0}, + {__LINE__, "2$*2$.*2$d", 2, &args[1], 20, 20, 0}, + {__LINE__, "2$*1$.*3$d", 2, &args[1], 10, 30, 0}, + {__LINE__, "2$*3$.*1$d", 2, &args[1], 30, 10, 0}, + {__LINE__, "1$*d", 0}, // indexed, then positional + {__LINE__, "*2$d", 0}, // positional, then indexed + {__LINE__, "6$d", 1}, // arg position out of bounds + {__LINE__, "1$6$d", 0}, // width position incorrectly specified + {__LINE__, "1$.6$d", 0}, // precision position incorrectly specified + {__LINE__, "1$*6$d", 1}, // width position out of bounds + {__LINE__, "1$.*6$d", 1}, // precision position out of bounds + }; +#pragma GCC diagnostic pop + for (const Expectation &e : kExpect) { + SCOPED_TRACE(e.line); + SCOPED_TRACE(e.fmt); + UnboundConversion props; + BoundConversion bound; + int ok_phases = 0; + int next = 0; + if (Extract(e.fmt, &props, &next)) { + ++ok_phases; + if (BindWithPack(&props, args, &bound)) { + ++ok_phases; + } + } + EXPECT_EQ(e.ok_phases, ok_phases); + if (e.ok_phases < 2) continue; + if (e.arg != nullptr) { + EXPECT_EQ(e.arg, bound.arg()); + } + EXPECT_EQ(e.width, bound.width()); + EXPECT_EQ(e.precision, bound.precision()); + } +} + +TEST_F(FormatBindTest, FormatPack) { + struct Expectation { + int line; + const char *fmt; + const char *summary; + }; + const int ia[] = { 10, 20, 30, 40, -10 }; + const FormatArgImpl args[] = {FormatArgImpl(ia[0]), FormatArgImpl(ia[1]), + FormatArgImpl(ia[2]), FormatArgImpl(ia[3]), + FormatArgImpl(ia[4])}; + const Expectation kExpect[] = { + {__LINE__, "a%4db%dc", "a{10:4d}b{20:d}c"}, + {__LINE__, "a%.4db%dc", "a{10:.4d}b{20:d}c"}, + {__LINE__, "a%4.5db%dc", "a{10:4.5d}b{20:d}c"}, + {__LINE__, "a%db%4.5dc", "a{10:d}b{20:4.5d}c"}, + {__LINE__, "a%db%*.*dc", "a{10:d}b{40:20.30d}c"}, + {__LINE__, "a%.*fb", "a{20:.10f}b"}, + {__LINE__, "a%1$db%2$*3$.*4$dc", "a{10:d}b{20:30.40d}c"}, + {__LINE__, "a%4$db%3$*2$.*1$dc", "a{40:d}b{30:20.10d}c"}, + {__LINE__, "a%04ldb", "a{10:04ld}b"}, + {__LINE__, "a%-#04lldb", "a{10:-#04lld}b"}, + {__LINE__, "a%1$*5$db", "a{10:-10d}b"}, + {__LINE__, "a%1$.*5$db", "a{10:d}b"}, + }; + for (const Expectation &e : kExpect) { + absl::string_view fmt = e.fmt; + SCOPED_TRACE(e.line); + SCOPED_TRACE(e.fmt); + UntypedFormatSpecImpl format(fmt); + EXPECT_EQ(e.summary, + str_format_internal::Summarize(format, absl::MakeSpan(args))) + << "line:" << e.line; + } +} + +} // namespace +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h new file mode 100644 index 00000000..d0191968 --- /dev/null +++ b/absl/strings/internal/str_format/checker.h @@ -0,0 +1,327 @@ +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ + +#include "absl/strings/internal/str_format/arg.h" +#include "absl/strings/internal/str_format/extension.h" + +// Compile time check support for entry points. + +#ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER +#if defined(__clang__) && !defined(__native_client__) +#if __has_attribute(enable_if) +#define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1 +#endif // __has_attribute(enable_if) +#endif // defined(__clang__) && !defined(__native_client__) +#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { + +constexpr bool AllOf() { return true; } + +template <typename... T> +constexpr bool AllOf(bool b, T... t) { + return b && AllOf(t...); +} + +template <typename Arg> +constexpr Conv ArgumentToConv() { + return decltype(str_format_internal::FormatConvertImpl( + std::declval<const Arg&>(), std::declval<const ConversionSpec&>(), + std::declval<FormatSinkImpl*>()))::kConv; +} + +#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + +constexpr bool ContainsChar(const char* chars, char c) { + return *chars == c || (*chars && ContainsChar(chars + 1, c)); +} + +// A constexpr compatible list of Convs. +struct ConvList { + const Conv* array; + int count; + + // We do the bound check here to avoid having to do it on the callers. + // Returning an empty Conv has the same effect as short circuiting because it + // will never match any conversion. + constexpr Conv operator[](int i) const { + return i < count ? array[i] : Conv{}; + } + + constexpr ConvList without_front() const { + return count != 0 ? ConvList{array + 1, count - 1} : *this; + } +}; + +template <size_t count> +struct ConvListT { + // Make sure the array has size > 0. + Conv list[count ? count : 1]; +}; + +constexpr char GetChar(string_view str, size_t index) { + return index < str.size() ? str[index] : char{}; +} + +constexpr string_view ConsumeFront(string_view str, size_t len = 1) { + return len <= str.size() ? string_view(str.data() + len, str.size() - len) + : string_view(); +} + +constexpr string_view ConsumeAnyOf(string_view format, const char* chars) { + return ContainsChar(chars, GetChar(format, 0)) + ? ConsumeAnyOf(ConsumeFront(format), chars) + : format; +} + +constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; } + +// Helper class for the ParseDigits function. +// It encapsulates the two return values we need there. +struct Integer { + string_view format; + int value; + + // If the next character is a '$', consume it. + // Otherwise, make `this` an invalid positional argument. + constexpr Integer ConsumePositionalDollar() const { + return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value} + : Integer{format, 0}; + } +}; + +constexpr Integer ParseDigits(string_view format, int value = 0) { + return IsDigit(GetChar(format, 0)) + ? ParseDigits(ConsumeFront(format), + 10 * value + GetChar(format, 0) - '0') + : Integer{format, value}; +} + +// Parse digits for a positional argument. +// The parsing also consumes the '$'. +constexpr Integer ParsePositional(string_view format) { + return ParseDigits(format).ConsumePositionalDollar(); +} + +// Parses a single conversion specifier. +// See ConvParser::Run() for post conditions. +class ConvParser { + constexpr ConvParser SetFormat(string_view format) const { + return ConvParser(format, args_, error_, arg_position_, is_positional_); + } + + constexpr ConvParser SetArgs(ConvList args) const { + return ConvParser(format_, args, error_, arg_position_, is_positional_); + } + + constexpr ConvParser SetError(bool error) const { + return ConvParser(format_, args_, error_ || error, arg_position_, + is_positional_); + } + + constexpr ConvParser SetArgPosition(int arg_position) const { + return ConvParser(format_, args_, error_, arg_position, is_positional_); + } + + // Consumes the next arg and verifies that it matches `conv`. + // `error_` is set if there is no next arg or if it doesn't match `conv`. + constexpr ConvParser ConsumeNextArg(char conv) const { + return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv)); + } + + // Verify that positional argument `i.value` matches `conv`. + // `error_` is set if `i.value` is not a valid argument or if it doesn't + // match. + constexpr ConvParser VerifyPositional(Integer i, char conv) const { + return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv)); + } + + // Parse the position of the arg and store it in `arg_position_`. + constexpr ConvParser ParseArgPosition(Integer arg) const { + return SetFormat(arg.format).SetArgPosition(arg.value); + } + + // Consume the flags. + constexpr ConvParser ParseFlags() const { + return SetFormat(ConsumeAnyOf(format_, "-+ #0")); + } + + // Consume the width. + // If it is '*', we verify that it matches `args_`. `error_` is set if it + // doesn't match. + constexpr ConvParser ParseWidth() const { + return IsDigit(GetChar(format_, 0)) + ? SetFormat(ParseDigits(format_).format) + : GetChar(format_, 0) == '*' + ? is_positional_ + ? VerifyPositional( + ParsePositional(ConsumeFront(format_)), '*') + : SetFormat(ConsumeFront(format_)) + .ConsumeNextArg('*') + : *this; + } + + // Consume the precision. + // If it is '*', we verify that it matches `args_`. `error_` is set if it + // doesn't match. + constexpr ConvParser ParsePrecision() const { + return GetChar(format_, 0) != '.' + ? *this + : GetChar(format_, 1) == '*' + ? is_positional_ + ? VerifyPositional( + ParsePositional(ConsumeFront(format_, 2)), '*') + : SetFormat(ConsumeFront(format_, 2)) + .ConsumeNextArg('*') + : SetFormat(ParseDigits(ConsumeFront(format_)).format); + } + + // Consume the length characters. + constexpr ConvParser ParseLength() const { + return SetFormat(ConsumeAnyOf(format_, "lLhjztq")); + } + + // Consume the conversion character and verify that it matches `args_`. + // `error_` is set if it doesn't match. + constexpr ConvParser ParseConversion() const { + return is_positional_ + ? VerifyPositional({ConsumeFront(format_), arg_position_}, + GetChar(format_, 0)) + : ConsumeNextArg(GetChar(format_, 0)) + .SetFormat(ConsumeFront(format_)); + } + + constexpr ConvParser(string_view format, ConvList args, bool error, + int arg_position, bool is_positional) + : format_(format), + args_(args), + error_(error), + arg_position_(arg_position), + is_positional_(is_positional) {} + + public: + constexpr ConvParser(string_view format, ConvList args, bool is_positional) + : format_(format), + args_(args), + error_(false), + arg_position_(0), + is_positional_(is_positional) {} + + // Consume the whole conversion specifier. + // `format()` will be set to the character after the conversion character. + // `error()` will be set if any of the arguments do not match. + constexpr ConvParser Run() const { + return (is_positional_ ? ParseArgPosition(ParsePositional(format_)) : *this) + .ParseFlags() + .ParseWidth() + .ParsePrecision() + .ParseLength() + .ParseConversion(); + } + + constexpr string_view format() const { return format_; } + constexpr ConvList args() const { return args_; } + constexpr bool error() const { return error_; } + constexpr bool is_positional() const { return is_positional_; } + + private: + string_view format_; + // Current list of arguments. If we are not in positional mode we will consume + // from the front. + ConvList args_; + bool error_; + // Holds the argument position of the conversion character, if we are in + // positional mode. Otherwise, it is unspecified. + int arg_position_; + // Whether we are in positional mode. + // It changes the behavior of '*' and where to find the converted argument. + bool is_positional_; +}; + +// Parses a whole format expression. +// See FormatParser::Run(). +class FormatParser { + static constexpr bool FoundPercent(string_view format) { + return format.empty() || + (GetChar(format, 0) == '%' && GetChar(format, 1) != '%'); + } + + // We use an inner function to increase the recursion limit. + // The inner function consumes up to `limit` characters on every run. + // This increases the limit from 512 to ~512*limit. + static constexpr string_view ConsumeNonPercentInner(string_view format, + int limit = 20) { + return FoundPercent(format) || !limit + ? format + : ConsumeNonPercentInner( + ConsumeFront(format, GetChar(format, 0) == '%' && + GetChar(format, 1) == '%' + ? 2 + : 1), + limit - 1); + } + + // Consume characters until the next conversion spec %. + // It skips %%. + static constexpr string_view ConsumeNonPercent(string_view format) { + return FoundPercent(format) + ? format + : ConsumeNonPercent(ConsumeNonPercentInner(format)); + } + + static constexpr bool IsPositional(string_view format) { + return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format)) + : GetChar(format, 0) == '$'; + } + + constexpr bool RunImpl(bool is_positional) const { + // In non-positional mode we require all arguments to be consumed. + // In positional mode just reaching the end of the format without errors is + // enough. + return (format_.empty() && (is_positional || args_.count == 0)) || + (!format_.empty() && + ValidateArg( + ConvParser(ConsumeFront(format_), args_, is_positional).Run())); + } + + constexpr bool ValidateArg(ConvParser conv) const { + return !conv.error() && FormatParser(conv.format(), conv.args()) + .RunImpl(conv.is_positional()); + } + + public: + constexpr FormatParser(string_view format, ConvList args) + : format_(ConsumeNonPercent(format)), args_(args) {} + + // Runs the parser for `format` and `args`. + // It verifies that the format is valid and that all conversion specifiers + // match the arguments passed. + // In non-positional mode it also verfies that all arguments are consumed. + constexpr bool Run() const { + return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_))); + } + + private: + string_view format_; + // Current list of arguments. + // If we are not in positional mode we will consume from the front and will + // have to be empty in the end. + ConvList args_; +}; + +template <Conv... C> +constexpr bool ValidFormatImpl(string_view format) { + return FormatParser(format, + {ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)}) + .Run(); +} + +#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc new file mode 100644 index 00000000..b4f38979 --- /dev/null +++ b/absl/strings/internal/str_format/checker_test.cc @@ -0,0 +1,152 @@ +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_format.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { +namespace { + +std::string ConvToString(Conv conv) { + std::string out; +#define CONV_SET_CASE(c) \ + if (Contains(conv, Conv::c)) out += #c; + ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, ) +#undef CONV_SET_CASE + if (Contains(conv, Conv::star)) out += "*"; + return out; +} + +TEST(StrFormatChecker, ArgumentToConv) { + Conv conv = ArgumentToConv<std::string>(); + EXPECT_EQ(ConvToString(conv), "s"); + + conv = ArgumentToConv<const char*>(); + EXPECT_EQ(ConvToString(conv), "sp"); + + conv = ArgumentToConv<double>(); + EXPECT_EQ(ConvToString(conv), "fFeEgGaA"); + + conv = ArgumentToConv<int>(); + EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaA*"); + + conv = ArgumentToConv<std::string*>(); + EXPECT_EQ(ConvToString(conv), "p"); +} + +#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + +struct Case { + bool result; + const char* format; +}; + +template <typename... Args> +constexpr Case ValidFormat(const char* format) { + return {ValidFormatImpl<ArgumentToConv<Args>()...>(format), format}; +} + +TEST(StrFormatChecker, ValidFormat) { + // We want to make sure these expressions are constexpr and they have the + // expected value. + // If they are not constexpr the attribute will just ignore them and not give + // a compile time error. + enum e {}; + enum class e2 {}; + constexpr Case trues[] = { + ValidFormat<>("abc"), // + + ValidFormat<e>("%d"), // + ValidFormat<e2>("%d"), // + ValidFormat<int>("%% %d"), // + ValidFormat<int>("%ld"), // + ValidFormat<int>("%lld"), // + ValidFormat<std::string>("%s"), // + ValidFormat<std::string>("%10s"), // + ValidFormat<int>("%.10x"), // + ValidFormat<int, int>("%*.3x"), // + ValidFormat<int>("%1.d"), // + ValidFormat<int>("%.d"), // + ValidFormat<int, double>("%d %g"), // + ValidFormat<int, std::string>("%*s"), // + ValidFormat<int, double>("%.*f"), // + ValidFormat<void (*)(), volatile int*>("%p %p"), // + ValidFormat<string_view, const char*, double, void*>( + "string_view=%s const char*=%s double=%f void*=%p)"), + + ValidFormat<int>("%% %1$d"), // + ValidFormat<int>("%1$ld"), // + ValidFormat<int>("%1$lld"), // + ValidFormat<std::string>("%1$s"), // + ValidFormat<std::string>("%1$10s"), // + ValidFormat<int>("%1$.10x"), // + ValidFormat<int>("%1$*1$.*1$d"), // + ValidFormat<int, int>("%1$*2$.3x"), // + ValidFormat<int>("%1$1.d"), // + ValidFormat<int>("%1$.d"), // + ValidFormat<double, int>("%2$d %1$g"), // + ValidFormat<int, std::string>("%2$*1$s"), // + ValidFormat<int, double>("%2$.*1$f"), // + ValidFormat<void*, string_view, const char*, double>( + "string_view=%2$s const char*=%3$s double=%4$f void*=%1$p " + "repeat=%3$s)")}; + + for (Case c : trues) { + EXPECT_TRUE(c.result) << c.format; + } + + constexpr Case falses[] = { + ValidFormat<int>(""), // + + ValidFormat<e>("%s"), // + ValidFormat<e2>("%s"), // + ValidFormat<>("%s"), // + ValidFormat<>("%r"), // + ValidFormat<int>("%s"), // + ValidFormat<int>("%.1.d"), // + ValidFormat<int>("%*1d"), // + ValidFormat<int>("%1-d"), // + ValidFormat<std::string, int>("%*s"), // + ValidFormat<int>("%*d"), // + ValidFormat<std::string>("%p"), // + ValidFormat<int (*)(int)>("%d"), // + + ValidFormat<>("%3$d"), // + ValidFormat<>("%1$r"), // + ValidFormat<int>("%1$s"), // + ValidFormat<int>("%1$.1.d"), // + ValidFormat<int>("%1$*2$1d"), // + ValidFormat<int>("%1$1-d"), // + ValidFormat<std::string, int>("%2$*1$s"), // + ValidFormat<std::string>("%1$p"), + + ValidFormat<int, int>("%d %2$d"), // + }; + + for (Case c : falses) { + EXPECT_FALSE(c.result) << c.format; + } +} + +TEST(StrFormatChecker, LongFormat) { +#define CHARS_X_40 "1234567890123456789012345678901234567890" +#define CHARS_X_400 \ + CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 \ + CHARS_X_40 CHARS_X_40 CHARS_X_40 +#define CHARS_X_4000 \ + CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 \ + CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 + constexpr char long_format[] = + CHARS_X_4000 "%d" CHARS_X_4000 "%s" CHARS_X_4000; + constexpr bool is_valid = ValidFormat<int, std::string>(long_format).result; + EXPECT_TRUE(is_valid); +} + +#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + +} // namespace +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc new file mode 100644 index 00000000..95d57b67 --- /dev/null +++ b/absl/strings/internal/str_format/convert_test.cc @@ -0,0 +1,577 @@ +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <cmath> +#include <string> + +#include "gtest/gtest.h" +#include "absl/strings/internal/str_format/bind.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { +namespace { + +template <typename T, size_t N> +size_t ArraySize(T (&)[N]) { + return N; +} + +std::string LengthModFor(float) { return ""; } +std::string LengthModFor(double) { return ""; } +std::string LengthModFor(long double) { return "L"; } +std::string LengthModFor(char) { return "hh"; } +std::string LengthModFor(signed char) { return "hh"; } +std::string LengthModFor(unsigned char) { return "hh"; } +std::string LengthModFor(short) { return "h"; } // NOLINT +std::string LengthModFor(unsigned short) { return "h"; } // NOLINT +std::string LengthModFor(int) { return ""; } +std::string LengthModFor(unsigned) { return ""; } +std::string LengthModFor(long) { return "l"; } // NOLINT +std::string LengthModFor(unsigned long) { return "l"; } // NOLINT +std::string LengthModFor(long long) { return "ll"; } // NOLINT +std::string LengthModFor(unsigned long long) { return "ll"; } // NOLINT + +std::string EscCharImpl(int v) { + if (isprint(v)) return std::string(1, static_cast<char>(v)); + char buf[64]; + int n = snprintf(buf, sizeof(buf), "\\%#.2x", + static_cast<unsigned>(v & 0xff)); + assert(n > 0 && n < sizeof(buf)); + return std::string(buf, n); +} + +std::string Esc(char v) { return EscCharImpl(v); } +std::string Esc(signed char v) { return EscCharImpl(v); } +std::string Esc(unsigned char v) { return EscCharImpl(v); } + +template <typename T> +std::string Esc(const T &v) { + std::ostringstream oss; + oss << v; + return oss.str(); +} + +void StrAppend(std::string *dst, const char *format, va_list ap) { + // First try with a small fixed size buffer + static const int kSpaceLength = 1024; + char space[kSpaceLength]; + + // It's possible for methods that use a va_list to invalidate + // the data in it upon use. The fix is to make a copy + // of the structure before using it and use that copy instead. + va_list backup_ap; + va_copy(backup_ap, ap); + int result = vsnprintf(space, kSpaceLength, format, backup_ap); + va_end(backup_ap); + if (result < kSpaceLength) { + if (result >= 0) { + // Normal case -- everything fit. + dst->append(space, result); + return; + } + if (result < 0) { + // Just an error. + return; + } + } + + // Increase the buffer size to the size requested by vsnprintf, + // plus one for the closing \0. + int length = result + 1; + char *buf = new char[length]; + + // Restore the va_list before we use it again + va_copy(backup_ap, ap); + result = vsnprintf(buf, length, format, backup_ap); + va_end(backup_ap); + + if (result >= 0 && result < length) { + // It fit + dst->append(buf, result); + } + delete[] buf; +} + +std::string StrPrint(const char *format, ...) { + va_list ap; + va_start(ap, format); + std::string result; + StrAppend(&result, format, ap); + va_end(ap); + return result; +} + +class FormatConvertTest : public ::testing::Test { }; + +template <typename T> +void TestStringConvert(const T& str) { + const FormatArgImpl args[] = {FormatArgImpl(str)}; + struct Expectation { + const char *out; + const char *fmt; + }; + const Expectation kExpect[] = { + {"hello", "%1$s" }, + {"", "%1$.s" }, + {"", "%1$.0s" }, + {"h", "%1$.1s" }, + {"he", "%1$.2s" }, + {"hello", "%1$.10s" }, + {" hello", "%1$6s" }, + {" he", "%1$5.2s" }, + {"he ", "%1$-5.2s" }, + {"hello ", "%1$-6.10s" }, + }; + for (const Expectation &e : kExpect) { + UntypedFormatSpecImpl format(e.fmt); + EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args))); + } +} + +TEST_F(FormatConvertTest, BasicString) { + TestStringConvert("hello"); // As char array. + TestStringConvert(static_cast<const char*>("hello")); + TestStringConvert(std::string("hello")); + TestStringConvert(string_view("hello")); +} + +TEST_F(FormatConvertTest, NullString) { + const char* p = nullptr; + UntypedFormatSpecImpl format("%s"); + EXPECT_EQ("", FormatPack(format, {FormatArgImpl(p)})); +} + +TEST_F(FormatConvertTest, StringPrecision) { + // We cap at the precision. + char c = 'a'; + const char* p = &c; + UntypedFormatSpecImpl format("%.1s"); + EXPECT_EQ("a", FormatPack(format, {FormatArgImpl(p)})); + + // We cap at the nul terminator. + p = "ABC"; + UntypedFormatSpecImpl format2("%.10s"); + EXPECT_EQ("ABC", FormatPack(format2, {FormatArgImpl(p)})); +} + +TEST_F(FormatConvertTest, Pointer) { +#if _MSC_VER + // MSVC's printf implementation prints pointers differently. We can't easily + // compare our implementation to theirs. + return; +#endif + static int x = 0; + const int *xp = &x; + char c = 'h'; + char *mcp = &c; + const char *cp = "hi"; + const char *cnil = nullptr; + const int *inil = nullptr; + using VoidF = void (*)(); + VoidF fp = [] {}, fnil = nullptr; + volatile char vc; + volatile char* vcp = &vc; + volatile char* vcnil = nullptr; + const FormatArgImpl args[] = { + FormatArgImpl(xp), FormatArgImpl(cp), FormatArgImpl(inil), + FormatArgImpl(cnil), FormatArgImpl(mcp), FormatArgImpl(fp), + FormatArgImpl(fnil), FormatArgImpl(vcp), FormatArgImpl(vcnil), + }; + struct Expectation { + std::string out; + const char *fmt; + }; + const Expectation kExpect[] = { + {StrPrint("%p", &x), "%p"}, + {StrPrint("%20p", &x), "%20p"}, + {StrPrint("%.1p", &x), "%.1p"}, + {StrPrint("%.20p", &x), "%.20p"}, + {StrPrint("%30.20p", &x), "%30.20p"}, + + {StrPrint("%-p", &x), "%-p"}, + {StrPrint("%-20p", &x), "%-20p"}, + {StrPrint("%-.1p", &x), "%-.1p"}, + {StrPrint("%.20p", &x), "%.20p"}, + {StrPrint("%-30.20p", &x), "%-30.20p"}, + + {StrPrint("%p", cp), "%2$p"}, // const char* + {"(nil)", "%3$p"}, // null const char * + {"(nil)", "%4$p"}, // null const int * + {StrPrint("%p", mcp), "%5$p"}, // nonconst char* + + {StrPrint("%p", fp), "%6$p"}, // function pointer + {StrPrint("%p", vcp), "%8$p"}, // function pointer + +#ifndef __APPLE__ + // Apple's printf differs here (0x0 vs. nil) + {StrPrint("%p", fnil), "%7$p"}, // null function pointer + {StrPrint("%p", vcnil), "%9$p"}, // null function pointer +#endif + }; + for (const Expectation &e : kExpect) { + UntypedFormatSpecImpl format(e.fmt); + EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args))) << e.fmt; + } +} + +struct Cardinal { + enum Pos { k1 = 1, k2 = 2, k3 = 3 }; + enum Neg { kM1 = -1, kM2 = -2, kM3 = -3 }; +}; + +TEST_F(FormatConvertTest, Enum) { + const Cardinal::Pos k3 = Cardinal::k3; + const Cardinal::Neg km3 = Cardinal::kM3; + const FormatArgImpl args[] = {FormatArgImpl(k3), FormatArgImpl(km3)}; + UntypedFormatSpecImpl format("%1$d"); + UntypedFormatSpecImpl format2("%2$d"); + EXPECT_EQ("3", FormatPack(format, absl::MakeSpan(args))); + EXPECT_EQ("-3", FormatPack(format2, absl::MakeSpan(args))); +} + +template <typename T> +class TypedFormatConvertTest : public FormatConvertTest { }; + +TYPED_TEST_CASE_P(TypedFormatConvertTest); + +std::vector<std::string> AllFlagCombinations() { + const char kFlags[] = {'-', '#', '0', '+', ' '}; + std::vector<std::string> result; + for (size_t fsi = 0; fsi < (1ull << ArraySize(kFlags)); ++fsi) { + std::string flag_set; + for (size_t fi = 0; fi < ArraySize(kFlags); ++fi) + if (fsi & (1ull << fi)) + flag_set += kFlags[fi]; + result.push_back(flag_set); + } + return result; +} + +TYPED_TEST_P(TypedFormatConvertTest, AllIntsWithFlags) { + typedef TypeParam T; + typedef typename std::make_unsigned<T>::type UnsignedT; + using remove_volatile_t = typename std::remove_volatile<T>::type; + const T kMin = std::numeric_limits<remove_volatile_t>::min(); + const T kMax = std::numeric_limits<remove_volatile_t>::max(); + const T kVals[] = { + remove_volatile_t(1), + remove_volatile_t(2), + remove_volatile_t(3), + remove_volatile_t(123), + remove_volatile_t(-1), + remove_volatile_t(-2), + remove_volatile_t(-3), + remove_volatile_t(-123), + remove_volatile_t(0), + kMax - remove_volatile_t(1), + kMax, + kMin + remove_volatile_t(1), + kMin, + }; + const char kConvChars[] = {'d', 'i', 'u', 'o', 'x', 'X'}; + const std::string kWid[] = {"", "4", "10"}; + const std::string kPrec[] = {"", ".", ".0", ".4", ".10"}; + + const std::vector<std::string> flag_sets = AllFlagCombinations(); + + for (size_t vi = 0; vi < ArraySize(kVals); ++vi) { + const T val = kVals[vi]; + SCOPED_TRACE(Esc(val)); + const FormatArgImpl args[] = {FormatArgImpl(val)}; + for (size_t ci = 0; ci < ArraySize(kConvChars); ++ci) { + const char conv_char = kConvChars[ci]; + for (size_t fsi = 0; fsi < flag_sets.size(); ++fsi) { + const std::string &flag_set = flag_sets[fsi]; + for (size_t wi = 0; wi < ArraySize(kWid); ++wi) { + const std::string &wid = kWid[wi]; + for (size_t pi = 0; pi < ArraySize(kPrec); ++pi) { + const std::string &prec = kPrec[pi]; + + const bool is_signed_conv = (conv_char == 'd' || conv_char == 'i'); + const bool is_unsigned_to_signed = + !std::is_signed<T>::value && is_signed_conv; + // Don't consider sign-related flags '+' and ' ' when doing + // unsigned to signed conversions. + if (is_unsigned_to_signed && + flag_set.find_first_of("+ ") != std::string::npos) { + continue; + } + + std::string new_fmt("%"); + new_fmt += flag_set; + new_fmt += wid; + new_fmt += prec; + // old and new always agree up to here. + std::string old_fmt = new_fmt; + new_fmt += conv_char; + std::string old_result; + if (is_unsigned_to_signed) { + // don't expect agreement on unsigned formatted as signed, + // as printf can't do that conversion properly. For those + // cases, we do expect agreement with printf with a "%u" + // and the unsigned equivalent of 'val'. + UnsignedT uval = val; + old_fmt += LengthModFor(uval); + old_fmt += "u"; + old_result = StrPrint(old_fmt.c_str(), uval); + } else { + old_fmt += LengthModFor(val); + old_fmt += conv_char; + old_result = StrPrint(old_fmt.c_str(), val); + } + + SCOPED_TRACE(std::string() + " old_fmt: \"" + old_fmt + + "\"'" + " new_fmt: \"" + + new_fmt + "\""); + UntypedFormatSpecImpl format(new_fmt); + EXPECT_EQ(old_result, FormatPack(format, absl::MakeSpan(args))); + } + } + } + } + } +} + +TYPED_TEST_P(TypedFormatConvertTest, Char) { + typedef TypeParam T; + using remove_volatile_t = typename std::remove_volatile<T>::type; + static const T kMin = std::numeric_limits<remove_volatile_t>::min(); + static const T kMax = std::numeric_limits<remove_volatile_t>::max(); + T kVals[] = { + remove_volatile_t(1), remove_volatile_t(2), remove_volatile_t(10), + remove_volatile_t(-1), remove_volatile_t(-2), remove_volatile_t(-10), + remove_volatile_t(0), + kMin + remove_volatile_t(1), kMin, + kMax - remove_volatile_t(1), kMax + }; + for (const T &c : kVals) { + const FormatArgImpl args[] = {FormatArgImpl(c)}; + UntypedFormatSpecImpl format("%c"); + EXPECT_EQ(StrPrint("%c", c), FormatPack(format, absl::MakeSpan(args))); + } +} + +REGISTER_TYPED_TEST_CASE_P(TypedFormatConvertTest, AllIntsWithFlags, Char); + +typedef ::testing::Types< + int, unsigned, volatile int, + short, unsigned short, + long, unsigned long, + long long, unsigned long long, + signed char, unsigned char, char> + AllIntTypes; +INSTANTIATE_TYPED_TEST_CASE_P(TypedFormatConvertTestWithAllIntTypes, + TypedFormatConvertTest, AllIntTypes); +TEST_F(FormatConvertTest, Uint128) { + absl::uint128 v = static_cast<absl::uint128>(0x1234567890abcdef) * 1979; + absl::uint128 max = absl::Uint128Max(); + const FormatArgImpl args[] = {FormatArgImpl(v), FormatArgImpl(max)}; + + struct Case { + const char* format; + const char* expected; + } cases[] = { + {"%1$d", "2595989796776606496405"}, + {"%1$30d", " 2595989796776606496405"}, + {"%1$-30d", "2595989796776606496405 "}, + {"%1$u", "2595989796776606496405"}, + {"%1$x", "8cba9876066020f695"}, + {"%2$d", "340282366920938463463374607431768211455"}, + {"%2$u", "340282366920938463463374607431768211455"}, + {"%2$x", "ffffffffffffffffffffffffffffffff"}, + }; + + for (auto c : cases) { + UntypedFormatSpecImpl format(c.format); + EXPECT_EQ(c.expected, FormatPack(format, absl::MakeSpan(args))); + } +} + +TEST_F(FormatConvertTest, Float) { +#if _MSC_VER + // MSVC has a different rounding policy than us so we can't test our + // implementation against the native one there. + return; +#endif // _MSC_VER + + const char *const kFormats[] = { + "%", "%.3", "%8.5", "%9", "%.60", "%.30", "%03", "%+", + "% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"}; + + std::vector<double> doubles = {0.0, + -0.0, + .99999999999999, + 99999999999999., + std::numeric_limits<double>::max(), + -std::numeric_limits<double>::max(), + std::numeric_limits<double>::min(), + -std::numeric_limits<double>::min(), + std::numeric_limits<double>::lowest(), + -std::numeric_limits<double>::lowest(), + std::numeric_limits<double>::epsilon(), + std::numeric_limits<double>::epsilon() + 1, + std::numeric_limits<double>::infinity(), + -std::numeric_limits<double>::infinity()}; + +#ifndef __APPLE__ + // Apple formats NaN differently (+nan) vs. (nan) + doubles.push_back(std::nan("")); +#endif + + // Some regression tests. + doubles.push_back(0.99999999999999989); + + if (std::numeric_limits<double>::has_denorm != std::denorm_absent) { + doubles.push_back(std::numeric_limits<double>::denorm_min()); + doubles.push_back(-std::numeric_limits<double>::denorm_min()); + } + + for (double base : + {1., 12., 123., 1234., 12345., 123456., 1234567., 12345678., 123456789., + 1234567890., 12345678901., 123456789012., 1234567890123.}) { + for (int exp = -123; exp <= 123; ++exp) { + for (int sign : {1, -1}) { + doubles.push_back(sign * std::ldexp(base, exp)); + } + } + } + + for (const char *fmt : kFormats) { + for (char f : {'f', 'F', // + 'g', 'G', // + 'a', 'A', // + 'e', 'E'}) { + std::string fmt_str = std::string(fmt) + f; + for (double d : doubles) { + int i = -10; + FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)}; + UntypedFormatSpecImpl format(fmt_str); + // We use ASSERT_EQ here because failures are usually correlated and a + // bug would print way too many failed expectations causing the test to + // time out. + ASSERT_EQ(StrPrint(fmt_str.c_str(), d, i), + FormatPack(format, absl::MakeSpan(args))) + << fmt_str << " " << StrPrint("%.18g", d) << " " + << StrPrint("%.999f", d); + } + } + } +} + +TEST_F(FormatConvertTest, LongDouble) { + const char *const kFormats[] = {"%", "%.3", "%8.5", "%9", + "%.60", "%+", "% ", "%-10"}; + + // This value is not representable in double, but it is in long double that + // uses the extended format. + // This is to verify that we are not truncating the value mistakenly through a + // double. + long double very_precise = 10000000000000000.25L; + + std::vector<long double> doubles = { + 0.0, + -0.0, + very_precise, + 1 / very_precise, + std::numeric_limits<long double>::max(), + -std::numeric_limits<long double>::max(), + std::numeric_limits<long double>::min(), + -std::numeric_limits<long double>::min(), + std::numeric_limits<long double>::infinity(), + -std::numeric_limits<long double>::infinity()}; + + for (const char *fmt : kFormats) { + for (char f : {'f', 'F', // + 'g', 'G', // + 'a', 'A', // + 'e', 'E'}) { + std::string fmt_str = std::string(fmt) + 'L' + f; + for (auto d : doubles) { + FormatArgImpl arg(d); + UntypedFormatSpecImpl format(fmt_str); + // We use ASSERT_EQ here because failures are usually correlated and a + // bug would print way too many failed expectations causing the test to + // time out. + ASSERT_EQ(StrPrint(fmt_str.c_str(), d), + FormatPack(format, {&arg, 1})) + << fmt_str << " " << StrPrint("%.18Lg", d) << " " + << StrPrint("%.999Lf", d); + } + } + } +} + +TEST_F(FormatConvertTest, IntAsFloat) { + const int kMin = std::numeric_limits<int>::min(); + const int kMax = std::numeric_limits<int>::max(); + const int ia[] = { + 1, 2, 3, 123, + -1, -2, -3, -123, + 0, kMax - 1, kMax, kMin + 1, kMin }; + for (const int fx : ia) { + SCOPED_TRACE(fx); + const FormatArgImpl args[] = {FormatArgImpl(fx)}; + struct Expectation { + int line; + std::string out; + const char *fmt; + }; + const double dx = static_cast<double>(fx); + const Expectation kExpect[] = { + { __LINE__, StrPrint("%f", dx), "%f" }, + { __LINE__, StrPrint("%12f", dx), "%12f" }, + { __LINE__, StrPrint("%.12f", dx), "%.12f" }, + { __LINE__, StrPrint("%12a", dx), "%12a" }, + { __LINE__, StrPrint("%.12a", dx), "%.12a" }, + }; + for (const Expectation &e : kExpect) { + SCOPED_TRACE(e.line); + SCOPED_TRACE(e.fmt); + UntypedFormatSpecImpl format(e.fmt); + EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args))); + } + } +} + +template <typename T> +bool FormatFails(const char* test_format, T value) { + std::string format_string = std::string("<<") + test_format + ">>"; + UntypedFormatSpecImpl format(format_string); + + int one = 1; + const FormatArgImpl args[] = {FormatArgImpl(value), FormatArgImpl(one)}; + EXPECT_EQ(FormatPack(format, absl::MakeSpan(args)), "") + << "format=" << test_format << " value=" << value; + return FormatPack(format, absl::MakeSpan(args)).empty(); +} + +TEST_F(FormatConvertTest, ExpectedFailures) { + // Int input + EXPECT_TRUE(FormatFails("%p", 1)); + EXPECT_TRUE(FormatFails("%s", 1)); + EXPECT_TRUE(FormatFails("%n", 1)); + + // Double input + EXPECT_TRUE(FormatFails("%p", 1.)); + EXPECT_TRUE(FormatFails("%s", 1.)); + EXPECT_TRUE(FormatFails("%n", 1.)); + EXPECT_TRUE(FormatFails("%c", 1.)); + EXPECT_TRUE(FormatFails("%d", 1.)); + EXPECT_TRUE(FormatFails("%x", 1.)); + EXPECT_TRUE(FormatFails("%*d", 1.)); + + // String input + EXPECT_TRUE(FormatFails("%n", "")); + EXPECT_TRUE(FormatFails("%c", "")); + EXPECT_TRUE(FormatFails("%d", "")); + EXPECT_TRUE(FormatFails("%x", "")); + EXPECT_TRUE(FormatFails("%f", "")); + EXPECT_TRUE(FormatFails("%*d", "")); +} + +} // namespace +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/extension.cc b/absl/strings/internal/str_format/extension.cc new file mode 100644 index 00000000..e3b41c82 --- /dev/null +++ b/absl/strings/internal/str_format/extension.cc @@ -0,0 +1,86 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/str_format/extension.h" + +#include <errno.h> +#include <algorithm> +#include <string> + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { +namespace { +// clang-format off +#define ABSL_LENGTH_MODS_EXPAND_ \ + X_VAL(h) X_SEP \ + X_VAL(hh) X_SEP \ + X_VAL(l) X_SEP \ + X_VAL(ll) X_SEP \ + X_VAL(L) X_SEP \ + X_VAL(j) X_SEP \ + X_VAL(z) X_SEP \ + X_VAL(t) X_SEP \ + X_VAL(q) +// clang-format on +} // namespace + +const LengthMod::Spec LengthMod::kSpecs[] = { +#define X_VAL(id) { LengthMod::id, #id, strlen(#id) } +#define X_SEP , + ABSL_LENGTH_MODS_EXPAND_, {LengthMod::none, "", 0} +#undef X_VAL +#undef X_SEP +}; + +const ConversionChar::Spec ConversionChar::kSpecs[] = { +#define X_VAL(id) { ConversionChar::id, #id[0] } +#define X_SEP , + ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP), + {ConversionChar::none, '\0'}, +#undef X_VAL +#undef X_SEP +}; + +std::string Flags::ToString() const { + std::string s; + s.append(left ? "-" : ""); + s.append(show_pos ? "+" : ""); + s.append(sign_col ? " " : ""); + s.append(alt ? "#" : ""); + s.append(zero ? "0" : ""); + return s; +} + +const size_t LengthMod::kNumValues; + +const size_t ConversionChar::kNumValues; + +bool FormatSinkImpl::PutPaddedString(string_view v, int w, int p, bool l) { + size_t space_remaining = 0; + if (w >= 0) space_remaining = w; + size_t n = v.size(); + if (p >= 0) n = std::min(n, static_cast<size_t>(p)); + string_view shown(v.data(), n); + space_remaining = Excess(shown.size(), space_remaining); + if (!l) Append(space_remaining, ' '); + Append(shown); + if (l) Append(space_remaining, ' '); + return true; +} + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h new file mode 100644 index 00000000..d401b4ed --- /dev/null +++ b/absl/strings/internal/str_format/extension.h @@ -0,0 +1,414 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_ + +#include <limits.h> +#include <cstddef> +#include <cstring> +#include <ostream> + +#include "absl/base/port.h" +#include "absl/strings/internal/str_format/output.h" +#include "absl/strings/string_view.h" + +class Cord; + +namespace absl { +inline namespace lts_2018_12_18 { + +namespace str_format_internal { + +class FormatRawSinkImpl { + public: + // Implicitly convert from any type that provides the hook function as + // described above. + template <typename T, decltype(str_format_internal::InvokeFlush( + std::declval<T*>(), string_view()))* = nullptr> + FormatRawSinkImpl(T* raw) // NOLINT + : sink_(raw), write_(&FormatRawSinkImpl::Flush<T>) {} + + void Write(string_view s) { write_(sink_, s); } + + template <typename T> + static FormatRawSinkImpl Extract(T s) { + return s.sink_; + } + + private: + template <typename T> + static void Flush(void* r, string_view s) { + str_format_internal::InvokeFlush(static_cast<T*>(r), s); + } + + void* sink_; + void (*write_)(void*, string_view); +}; + +// An abstraction to which conversions write their string data. +class FormatSinkImpl { + public: + explicit FormatSinkImpl(FormatRawSinkImpl raw) : raw_(raw) {} + + ~FormatSinkImpl() { Flush(); } + + void Flush() { + raw_.Write(string_view(buf_, pos_ - buf_)); + pos_ = buf_; + } + + void Append(size_t n, char c) { + if (n == 0) return; + size_ += n; + auto raw_append = [&](size_t count) { + memset(pos_, c, count); + pos_ += count; + }; + while (n > Avail()) { + n -= Avail(); + if (Avail() > 0) { + raw_append(Avail()); + } + Flush(); + } + raw_append(n); + } + + void Append(string_view v) { + size_t n = v.size(); + if (n == 0) return; + size_ += n; + if (n >= Avail()) { + Flush(); + raw_.Write(v); + return; + } + memcpy(pos_, v.data(), n); + pos_ += n; + } + + size_t size() const { return size_; } + + // Put 'v' to 'sink' with specified width, precision, and left flag. + bool PutPaddedString(string_view v, int w, int p, bool l); + + template <typename T> + T Wrap() { + return T(this); + } + + template <typename T> + static FormatSinkImpl* Extract(T* s) { + return s->sink_; + } + + private: + size_t Avail() const { return buf_ + sizeof(buf_) - pos_; } + + FormatRawSinkImpl raw_; + size_t size_ = 0; + char* pos_ = buf_; + char buf_[1024]; +}; + +struct Flags { + bool basic : 1; // fastest conversion: no flags, width, or precision + bool left : 1; // "-" + bool show_pos : 1; // "+" + bool sign_col : 1; // " " + bool alt : 1; // "#" + bool zero : 1; // "0" + std::string ToString() const; + friend std::ostream& operator<<(std::ostream& os, const Flags& v) { + return os << v.ToString(); + } +}; + +struct LengthMod { + public: + enum Id : uint8_t { + h, hh, l, ll, L, j, z, t, q, none + }; + static const size_t kNumValues = none + 1; + + LengthMod() : id_(none) {} + + // Index into the opaque array of LengthMod enums. + // Requires: i < kNumValues + static LengthMod FromIndex(size_t i) { + return LengthMod(kSpecs[i].value); + } + + static LengthMod FromId(Id id) { return LengthMod(id); } + + // The length modifier std::string associated with a specified LengthMod. + string_view name() const { + const Spec& spec = kSpecs[id_]; + return {spec.name, spec.name_length}; + } + + Id id() const { return id_; } + + friend bool operator==(const LengthMod& a, const LengthMod& b) { + return a.id() == b.id(); + } + friend bool operator!=(const LengthMod& a, const LengthMod& b) { + return !(a == b); + } + friend std::ostream& operator<<(std::ostream& os, const LengthMod& v) { + return os << v.name(); + } + + private: + struct Spec { + Id value; + const char *name; + size_t name_length; + }; + static const Spec kSpecs[]; + + explicit LengthMod(Id id) : id_(id) {} + + Id id_; +}; + +// clang-format off +#define ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP) \ + /* text */ \ + X_VAL(c) X_SEP X_VAL(C) X_SEP X_VAL(s) X_SEP X_VAL(S) X_SEP \ + /* ints */ \ + X_VAL(d) X_SEP X_VAL(i) X_SEP X_VAL(o) X_SEP \ + X_VAL(u) X_SEP X_VAL(x) X_SEP X_VAL(X) X_SEP \ + /* floats */ \ + X_VAL(f) X_SEP X_VAL(F) X_SEP X_VAL(e) X_SEP X_VAL(E) X_SEP \ + X_VAL(g) X_SEP X_VAL(G) X_SEP X_VAL(a) X_SEP X_VAL(A) X_SEP \ + /* misc */ \ + X_VAL(n) X_SEP X_VAL(p) +// clang-format on + +struct ConversionChar { + public: + enum Id : uint8_t { + c, C, s, S, // text + d, i, o, u, x, X, // int + f, F, e, E, g, G, a, A, // float + n, p, // misc + none + }; + static const size_t kNumValues = none + 1; + + ConversionChar() : id_(none) {} + + public: + // Index into the opaque array of ConversionChar enums. + // Requires: i < kNumValues + static ConversionChar FromIndex(size_t i) { + return ConversionChar(kSpecs[i].value); + } + + static ConversionChar FromChar(char c) { + ConversionChar::Id out_id = ConversionChar::none; + switch (c) { +#define X_VAL(id) \ + case #id[0]: \ + out_id = ConversionChar::id; \ + break; + ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, ) +#undef X_VAL + default: + break; + } + return ConversionChar(out_id); + } + + static ConversionChar FromId(Id id) { return ConversionChar(id); } + Id id() const { return id_; } + + int radix() const { + switch (id()) { + case x: case X: case a: case A: case p: return 16; + case o: return 8; + default: return 10; + } + } + + bool upper() const { + switch (id()) { + case X: case F: case E: case G: case A: return true; + default: return false; + } + } + + bool is_signed() const { + switch (id()) { + case d: case i: return true; + default: return false; + } + } + + bool is_integral() const { + switch (id()) { + case d: case i: case u: case o: case x: case X: + return true; + default: return false; + } + } + + bool is_float() const { + switch (id()) { + case a: case e: case f: case g: case A: case E: case F: case G: + return true; + default: return false; + } + } + + bool IsValid() const { return id() != none; } + + // The associated char. + char Char() const { return kSpecs[id_].name; } + + friend bool operator==(const ConversionChar& a, const ConversionChar& b) { + return a.id() == b.id(); + } + friend bool operator!=(const ConversionChar& a, const ConversionChar& b) { + return !(a == b); + } + friend std::ostream& operator<<(std::ostream& os, const ConversionChar& v) { + char c = v.Char(); + if (!c) c = '?'; + return os << c; + } + + private: + struct Spec { + Id value; + char name; + }; + static const Spec kSpecs[]; + + explicit ConversionChar(Id id) : id_(id) {} + + Id id_; +}; + +class ConversionSpec { + public: + Flags flags() const { return flags_; } + LengthMod length_mod() const { return length_mod_; } + ConversionChar conv() const { + // Keep this field first in the struct . It generates better code when + // accessing it when ConversionSpec is passed by value in registers. + static_assert(offsetof(ConversionSpec, conv_) == 0, ""); + return conv_; + } + + // Returns the specified width. If width is unspecfied, it returns a negative + // value. + int width() const { return width_; } + // Returns the specified precision. If precision is unspecfied, it returns a + // negative value. + int precision() const { return precision_; } + + void set_flags(Flags f) { flags_ = f; } + void set_length_mod(LengthMod lm) { length_mod_ = lm; } + void set_conv(ConversionChar c) { conv_ = c; } + void set_width(int w) { width_ = w; } + void set_precision(int p) { precision_ = p; } + void set_left(bool b) { flags_.left = b; } + + private: + ConversionChar conv_; + Flags flags_; + LengthMod length_mod_; + int width_; + int precision_; +}; + +constexpr uint64_t ConversionCharToConvValue(char conv) { + return +#define CONV_SET_CASE(c) \ + conv == #c[0] ? (uint64_t{1} << (1 + ConversionChar::Id::c)): + ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, ) +#undef CONV_SET_CASE + conv == '*' + ? 1 + : 0; +} + +enum class Conv : uint64_t { +#define CONV_SET_CASE(c) c = ConversionCharToConvValue(#c[0]), + ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, ) +#undef CONV_SET_CASE + + // Used for width/precision '*' specification. + star = ConversionCharToConvValue('*'), + + // Some predefined values: + integral = d | i | u | o | x | X, + floating = a | e | f | g | A | E | F | G, + numeric = integral | floating, + string = s, // absl:ignore(std::string) + pointer = p +}; + +// Type safe OR operator. +// We need this for two reasons: +// 1. operator| on enums makes them decay to integers and the result is an +// integer. We need the result to stay as an enum. +// 2. We use "enum class" which would not work even if we accepted the decay. +constexpr Conv operator|(Conv a, Conv b) { + return Conv(static_cast<uint64_t>(a) | static_cast<uint64_t>(b)); +} + +// Get a conversion with a single character in it. +constexpr Conv ConversionCharToConv(char c) { + return Conv(ConversionCharToConvValue(c)); +} + +// Checks whether `c` exists in `set`. +constexpr bool Contains(Conv set, char c) { + return (static_cast<uint64_t>(set) & ConversionCharToConvValue(c)) != 0; +} + +// Checks whether all the characters in `c` are contained in `set` +constexpr bool Contains(Conv set, Conv c) { + return (static_cast<uint64_t>(set) & static_cast<uint64_t>(c)) == + static_cast<uint64_t>(c); +} + +// Return type of the AbslFormatConvert() functions. +// The Conv template parameter is used to inform the framework of what +// conversion characters are supported by that AbslFormatConvert routine. +template <Conv C> +struct ConvertResult { + static constexpr Conv kConv = C; + bool value; +}; +template <Conv C> +constexpr Conv ConvertResult<C>::kConv; + +// Return capacity - used, clipped to a minimum of 0. +inline size_t Excess(size_t used, size_t capacity) { + return used < capacity ? capacity - used : 0; +} + +} // namespace str_format_internal + +} // inline namespace lts_2018_12_18 +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_ diff --git a/absl/strings/internal/str_format/extension_test.cc b/absl/strings/internal/str_format/extension_test.cc new file mode 100644 index 00000000..224fc923 --- /dev/null +++ b/absl/strings/internal/str_format/extension_test.cc @@ -0,0 +1,65 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "absl/strings/internal/str_format/extension.h" + +#include <random> +#include <string> +#include "absl/strings/str_format.h" + +#include "gtest/gtest.h" + +namespace { + +std::string MakeRandomString(size_t len) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis('a', 'z'); + std::string s(len, '0'); + for (char& c : s) { + c = dis(gen); + } + return s; +} + +TEST(FormatExtensionTest, SinkAppendSubstring) { + for (size_t chunk_size : {1, 10, 100, 1000, 10000}) { + std::string expected, actual; + absl::str_format_internal::FormatSinkImpl sink(&actual); + for (size_t chunks = 0; chunks < 10; ++chunks) { + std::string rand = MakeRandomString(chunk_size); + expected += rand; + sink.Append(rand); + } + sink.Flush(); + EXPECT_EQ(actual, expected); + } +} + +TEST(FormatExtensionTest, SinkAppendChars) { + for (size_t chunk_size : {1, 10, 100, 1000, 10000}) { + std::string expected, actual; + absl::str_format_internal::FormatSinkImpl sink(&actual); + for (size_t chunks = 0; chunks < 10; ++chunks) { + std::string rand = MakeRandomString(1); + expected.append(chunk_size, rand[0]); + sink.Append(chunk_size, rand[0]); + } + sink.Flush(); + EXPECT_EQ(actual, expected); + } +} +} // namespace diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc new file mode 100644 index 00000000..7b617689 --- /dev/null +++ b/absl/strings/internal/str_format/float_conversion.cc @@ -0,0 +1,485 @@ +#include "absl/strings/internal/str_format/float_conversion.h" + +#include <string.h> +#include <algorithm> +#include <cassert> +#include <cmath> +#include <string> + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { + +namespace { + +char *CopyStringTo(string_view v, char *out) { + std::memcpy(out, v.data(), v.size()); + return out + v.size(); +} + +template <typename Float> +bool FallbackToSnprintf(const Float v, const ConversionSpec &conv, + FormatSinkImpl *sink) { + int w = conv.width() >= 0 ? conv.width() : 0; + int p = conv.precision() >= 0 ? conv.precision() : -1; + char fmt[32]; + { + char *fp = fmt; + *fp++ = '%'; + fp = CopyStringTo(conv.flags().ToString(), fp); + fp = CopyStringTo("*.*", fp); + if (std::is_same<long double, Float>()) { + *fp++ = 'L'; + } + *fp++ = conv.conv().Char(); + *fp = 0; + assert(fp < fmt + sizeof(fmt)); + } + std::string space(512, '\0'); + string_view result; + while (true) { + int n = snprintf(&space[0], space.size(), fmt, w, p, v); + if (n < 0) return false; + if (static_cast<size_t>(n) < space.size()) { + result = string_view(space.data(), n); + break; + } + space.resize(n + 1); + } + sink->Append(result); + return true; +} + +// 128-bits in decimal: ceil(128*log(2)/log(10)) +// or std::numeric_limits<__uint128_t>::digits10 +constexpr int kMaxFixedPrecision = 39; + +constexpr int kBufferLength = /*sign*/ 1 + + /*integer*/ kMaxFixedPrecision + + /*point*/ 1 + + /*fraction*/ kMaxFixedPrecision + + /*exponent e+123*/ 5; + +struct Buffer { + void push_front(char c) { + assert(begin > data); + *--begin = c; + } + void push_back(char c) { + assert(end < data + sizeof(data)); + *end++ = c; + } + void pop_back() { + assert(begin < end); + --end; + } + + char &back() { + assert(begin < end); + return end[-1]; + } + + char last_digit() const { return end[-1] == '.' ? end[-2] : end[-1]; } + + int size() const { return static_cast<int>(end - begin); } + + char data[kBufferLength]; + char *begin; + char *end; +}; + +enum class FormatStyle { Fixed, Precision }; + +// If the value is Inf or Nan, print it and return true. +// Otherwise, return false. +template <typename Float> +bool ConvertNonNumericFloats(char sign_char, Float v, + const ConversionSpec &conv, FormatSinkImpl *sink) { + char text[4], *ptr = text; + if (sign_char) *ptr++ = sign_char; + if (std::isnan(v)) { + ptr = std::copy_n(conv.conv().upper() ? "NAN" : "nan", 3, ptr); + } else if (std::isinf(v)) { + ptr = std::copy_n(conv.conv().upper() ? "INF" : "inf", 3, ptr); + } else { + return false; + } + + return sink->PutPaddedString(string_view(text, ptr - text), conv.width(), -1, + conv.flags().left); +} + +// Round up the last digit of the value. +// It will carry over and potentially overflow. 'exp' will be adjusted in that +// case. +template <FormatStyle mode> +void RoundUp(Buffer *buffer, int *exp) { + char *p = &buffer->back(); + while (p >= buffer->begin && (*p == '9' || *p == '.')) { + if (*p == '9') *p = '0'; + --p; + } + + if (p < buffer->begin) { + *p = '1'; + buffer->begin = p; + if (mode == FormatStyle::Precision) { + std::swap(p[1], p[2]); // move the . + ++*exp; + buffer->pop_back(); + } + } else { + ++*p; + } +} + +void PrintExponent(int exp, char e, Buffer *out) { + out->push_back(e); + if (exp < 0) { + out->push_back('-'); + exp = -exp; + } else { + out->push_back('+'); + } + // Exponent digits. + if (exp > 99) { + out->push_back(exp / 100 + '0'); + out->push_back(exp / 10 % 10 + '0'); + out->push_back(exp % 10 + '0'); + } else { + out->push_back(exp / 10 + '0'); + out->push_back(exp % 10 + '0'); + } +} + +template <typename Float, typename Int> +constexpr bool CanFitMantissa() { + return +#if defined(__clang__) && !defined(__SSE3__) + // Workaround for clang bug: https://bugs.llvm.org/show_bug.cgi?id=38289 + // Casting from long double to uint64_t is miscompiled and drops bits. + (!std::is_same<Float, long double>::value || + !std::is_same<Int, uint64_t>::value) && +#endif + std::numeric_limits<Float>::digits <= std::numeric_limits<Int>::digits; +} + +template <typename Float> +struct Decomposed { + Float mantissa; + int exponent; +}; + +// Decompose the double into an integer mantissa and an exponent. +template <typename Float> +Decomposed<Float> Decompose(Float v) { + int exp; + Float m = std::frexp(v, &exp); + m = std::ldexp(m, std::numeric_limits<Float>::digits); + exp -= std::numeric_limits<Float>::digits; + return {m, exp}; +} + +// Print 'digits' as decimal. +// In Fixed mode, we add a '.' at the end. +// In Precision mode, we add a '.' after the first digit. +template <FormatStyle mode, typename Int> +int PrintIntegralDigits(Int digits, Buffer *out) { + int printed = 0; + if (digits) { + for (; digits; digits /= 10) out->push_front(digits % 10 + '0'); + printed = out->size(); + if (mode == FormatStyle::Precision) { + out->push_front(*out->begin); + out->begin[1] = '.'; + } else { + out->push_back('.'); + } + } else if (mode == FormatStyle::Fixed) { + out->push_front('0'); + out->push_back('.'); + printed = 1; + } + return printed; +} + +// Back out 'extra_digits' digits and round up if necessary. +bool RemoveExtraPrecision(int extra_digits, bool has_leftover_value, + Buffer *out, int *exp_out) { + if (extra_digits <= 0) return false; + + // Back out the extra digits + out->end -= extra_digits; + + bool needs_to_round_up = [&] { + // We look at the digit just past the end. + // There must be 'extra_digits' extra valid digits after end. + if (*out->end > '5') return true; + if (*out->end < '5') return false; + if (has_leftover_value || std::any_of(out->end + 1, out->end + extra_digits, + [](char c) { return c != '0'; })) + return true; + + // Ends in ...50*, round to even. + return out->last_digit() % 2 == 1; + }(); + + if (needs_to_round_up) { + RoundUp<FormatStyle::Precision>(out, exp_out); + } + return true; +} + +// Print the value into the buffer. +// This will not include the exponent, which will be returned in 'exp_out' for +// Precision mode. +template <typename Int, typename Float, FormatStyle mode> +bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out, + int *exp_out) { + assert((CanFitMantissa<Float, Int>())); + + const int int_bits = std::numeric_limits<Int>::digits; + + // In precision mode, we start printing one char to the right because it will + // also include the '.' + // In fixed mode we put the dot afterwards on the right. + out->begin = out->end = + out->data + 1 + kMaxFixedPrecision + (mode == FormatStyle::Precision); + + if (exp >= 0) { + if (std::numeric_limits<Float>::digits + exp > int_bits) { + // The value will overflow the Int + return false; + } + int digits_printed = PrintIntegralDigits<mode>(int_mantissa << exp, out); + int digits_to_zero_pad = precision; + if (mode == FormatStyle::Precision) { + *exp_out = digits_printed - 1; + digits_to_zero_pad -= digits_printed - 1; + if (RemoveExtraPrecision(-digits_to_zero_pad, false, out, exp_out)) { + return true; + } + } + for (; digits_to_zero_pad-- > 0;) out->push_back('0'); + return true; + } + + exp = -exp; + // We need at least 4 empty bits for the next decimal digit. + // We will multiply by 10. + if (exp > int_bits - 4) return false; + + const Int mask = (Int{1} << exp) - 1; + + // Print the integral part first. + int digits_printed = PrintIntegralDigits<mode>(int_mantissa >> exp, out); + int_mantissa &= mask; + + int fractional_count = precision; + if (mode == FormatStyle::Precision) { + if (digits_printed == 0) { + // Find the first non-zero digit, when in Precision mode. + *exp_out = 0; + if (int_mantissa) { + while (int_mantissa <= mask) { + int_mantissa *= 10; + --*exp_out; + } + } + out->push_front(static_cast<char>(int_mantissa >> exp) + '0'); + out->push_back('.'); + int_mantissa &= mask; + } else { + // We already have a digit, and a '.' + *exp_out = digits_printed - 1; + fractional_count -= *exp_out; + if (RemoveExtraPrecision(-fractional_count, int_mantissa != 0, out, + exp_out)) { + // If we had enough digits, return right away. + // The code below will try to round again otherwise. + return true; + } + } + } + + auto get_next_digit = [&] { + int_mantissa *= 10; + int digit = static_cast<int>(int_mantissa >> exp); + int_mantissa &= mask; + return digit; + }; + + // Print fractional_count more digits, if available. + for (; fractional_count > 0; --fractional_count) { + out->push_back(get_next_digit() + '0'); + } + + int next_digit = get_next_digit(); + if (next_digit > 5 || + (next_digit == 5 && (int_mantissa || out->last_digit() % 2 == 1))) { + RoundUp<mode>(out, exp_out); + } + + return true; +} + +template <FormatStyle mode, typename Float> +bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out, + int *exp) { + if (precision > kMaxFixedPrecision) return false; + + // Try with uint64_t. + if (CanFitMantissa<Float, std::uint64_t>() && + FloatToBufferImpl<std::uint64_t, Float, mode>( + static_cast<std::uint64_t>(decomposed.mantissa), + static_cast<std::uint64_t>(decomposed.exponent), precision, out, exp)) + return true; + +#if defined(__SIZEOF_INT128__) + // If that is not enough, try with __uint128_t. + return CanFitMantissa<Float, __uint128_t>() && + FloatToBufferImpl<__uint128_t, Float, mode>( + static_cast<__uint128_t>(decomposed.mantissa), + static_cast<__uint128_t>(decomposed.exponent), precision, out, + exp); +#endif + return false; +} + +void WriteBufferToSink(char sign_char, string_view str, + const ConversionSpec &conv, FormatSinkImpl *sink) { + int left_spaces = 0, zeros = 0, right_spaces = 0; + int missing_chars = + conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) - + static_cast<int>(sign_char != 0), + 0) + : 0; + if (conv.flags().left) { + right_spaces = missing_chars; + } else if (conv.flags().zero) { + zeros = missing_chars; + } else { + left_spaces = missing_chars; + } + + sink->Append(left_spaces, ' '); + if (sign_char) sink->Append(1, sign_char); + sink->Append(zeros, '0'); + sink->Append(str); + sink->Append(right_spaces, ' '); +} + +template <typename Float> +bool FloatToSink(const Float v, const ConversionSpec &conv, + FormatSinkImpl *sink) { + // Print the sign or the sign column. + Float abs_v = v; + char sign_char = 0; + if (std::signbit(abs_v)) { + sign_char = '-'; + abs_v = -abs_v; + } else if (conv.flags().show_pos) { + sign_char = '+'; + } else if (conv.flags().sign_col) { + sign_char = ' '; + } + + // Print nan/inf. + if (ConvertNonNumericFloats(sign_char, abs_v, conv, sink)) { + return true; + } + + int precision = conv.precision() < 0 ? 6 : conv.precision(); + + int exp = 0; + + auto decomposed = Decompose(abs_v); + + Buffer buffer; + + switch (conv.conv().id()) { + case ConversionChar::f: + case ConversionChar::F: + if (!FloatToBuffer<FormatStyle::Fixed>(decomposed, precision, &buffer, + nullptr)) { + return FallbackToSnprintf(v, conv, sink); + } + if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back(); + break; + + case ConversionChar::e: + case ConversionChar::E: + if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer, + &exp)) { + return FallbackToSnprintf(v, conv, sink); + } + if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back(); + PrintExponent(exp, conv.conv().upper() ? 'E' : 'e', &buffer); + break; + + case ConversionChar::g: + case ConversionChar::G: + precision = std::max(0, precision - 1); + if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer, + &exp)) { + return FallbackToSnprintf(v, conv, sink); + } + if (precision + 1 > exp && exp >= -4) { + if (exp < 0) { + // Have 1.23456, needs 0.00123456 + // Move the first digit + buffer.begin[1] = *buffer.begin; + // Add some zeros + for (; exp < -1; ++exp) *buffer.begin-- = '0'; + *buffer.begin-- = '.'; + *buffer.begin = '0'; + } else if (exp > 0) { + // Have 1.23456, needs 1234.56 + // Move the '.' exp positions to the right. + std::rotate(buffer.begin + 1, buffer.begin + 2, + buffer.begin + exp + 2); + } + exp = 0; + } + if (!conv.flags().alt) { + while (buffer.back() == '0') buffer.pop_back(); + if (buffer.back() == '.') buffer.pop_back(); + } + if (exp) PrintExponent(exp, conv.conv().upper() ? 'E' : 'e', &buffer); + break; + + case ConversionChar::a: + case ConversionChar::A: + return FallbackToSnprintf(v, conv, sink); + + default: + return false; + } + + WriteBufferToSink(sign_char, + string_view(buffer.begin, buffer.end - buffer.begin), conv, + sink); + + return true; +} + +} // namespace + +bool ConvertFloatImpl(long double v, const ConversionSpec &conv, + FormatSinkImpl *sink) { + return FloatToSink(v, conv, sink); +} + +bool ConvertFloatImpl(float v, const ConversionSpec &conv, + FormatSinkImpl *sink) { + return FloatToSink(v, conv, sink); +} + +bool ConvertFloatImpl(double v, const ConversionSpec &conv, + FormatSinkImpl *sink) { + return FloatToSink(v, conv, sink); +} + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/float_conversion.h b/absl/strings/internal/str_format/float_conversion.h new file mode 100644 index 00000000..280c471a --- /dev/null +++ b/absl/strings/internal/str_format/float_conversion.h @@ -0,0 +1,23 @@ +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_ + +#include "absl/strings/internal/str_format/extension.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { + +bool ConvertFloatImpl(float v, const ConversionSpec &conv, + FormatSinkImpl *sink); + +bool ConvertFloatImpl(double v, const ConversionSpec &conv, + FormatSinkImpl *sink); + +bool ConvertFloatImpl(long double v, const ConversionSpec &conv, + FormatSinkImpl *sink); + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_ diff --git a/absl/strings/internal/str_format/output.cc b/absl/strings/internal/str_format/output.cc new file mode 100644 index 00000000..010bf341 --- /dev/null +++ b/absl/strings/internal/str_format/output.cc @@ -0,0 +1,72 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/str_format/output.h" + +#include <errno.h> +#include <cstring> + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { + +namespace { +struct ClearErrnoGuard { + ClearErrnoGuard() : old_value(errno) { errno = 0; } + ~ClearErrnoGuard() { + if (!errno) errno = old_value; + } + int old_value; +}; +} // namespace + +void BufferRawSink::Write(string_view v) { + size_t to_write = std::min(v.size(), size_); + std::memcpy(buffer_, v.data(), to_write); + buffer_ += to_write; + size_ -= to_write; + total_written_ += v.size(); +} + +void FILERawSink::Write(string_view v) { + while (!v.empty() && !error_) { + // Reset errno to zero in case the libc implementation doesn't set errno + // when a failure occurs. + ClearErrnoGuard guard; + + if (size_t result = std::fwrite(v.data(), 1, v.size(), output_)) { + // Some progress was made. + count_ += result; + v.remove_prefix(result); + } else { + if (errno == EINTR) { + continue; + } else if (errno) { + error_ = errno; + } else if (std::ferror(output_)) { + // Non-POSIX compliant libc implementations may not set errno, so we + // have check the streams error indicator. + error_ = EBADF; + } else { + // We're likely on a non-POSIX system that encountered EINTR but had no + // way of reporting it. + continue; + } + } + } +} + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/output.h b/absl/strings/internal/str_format/output.h new file mode 100644 index 00000000..0f3ab349 --- /dev/null +++ b/absl/strings/internal/str_format/output.h @@ -0,0 +1,103 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Output extension hooks for the Format library. +// `internal::InvokeFlush` calls the appropriate flush function for the +// specified output argument. +// `BufferRawSink` is a simple output sink for a char buffer. Used by SnprintF. +// `FILERawSink` is a std::FILE* based sink. Used by PrintF and FprintF. + +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_ + +#include <cstdio> +#include <ostream> +#include <string> + +#include "absl/base/port.h" +#include "absl/strings/string_view.h" + +class Cord; + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { + +// RawSink implementation that writes into a char* buffer. +// It will not overflow the buffer, but will keep the total count of chars +// that would have been written. +class BufferRawSink { + public: + BufferRawSink(char* buffer, size_t size) : buffer_(buffer), size_(size) {} + + size_t total_written() const { return total_written_; } + void Write(string_view v); + + private: + char* buffer_; + size_t size_; + size_t total_written_ = 0; +}; + +// RawSink implementation that writes into a FILE*. +// It keeps track of the total number of bytes written and any error encountered +// during the writes. +class FILERawSink { + public: + explicit FILERawSink(std::FILE* output) : output_(output) {} + + void Write(string_view v); + + size_t count() const { return count_; } + int error() const { return error_; } + + private: + std::FILE* output_; + int error_ = 0; + size_t count_ = 0; +}; + +// Provide RawSink integration with common types from the STL. +inline void AbslFormatFlush(std::string* out, string_view s) { + out->append(s.data(), s.size()); +} +inline void AbslFormatFlush(std::ostream* out, string_view s) { + out->write(s.data(), s.size()); +} + +template <class AbslCord, typename = typename std::enable_if< + std::is_same<AbslCord, ::Cord>::value>::type> +inline void AbslFormatFlush(AbslCord* out, string_view s) { + out->Append(s); +} + +inline void AbslFormatFlush(FILERawSink* sink, string_view v) { + sink->Write(v); +} + +inline void AbslFormatFlush(BufferRawSink* sink, string_view v) { + sink->Write(v); +} + +template <typename T> +auto InvokeFlush(T* out, string_view s) + -> decltype(str_format_internal::AbslFormatFlush(out, s)) { + str_format_internal::AbslFormatFlush(out, s); +} + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_ diff --git a/absl/strings/internal/str_format/output_test.cc b/absl/strings/internal/str_format/output_test.cc new file mode 100644 index 00000000..0a014cac --- /dev/null +++ b/absl/strings/internal/str_format/output_test.cc @@ -0,0 +1,80 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/str_format/output.h" + +#include <sstream> +#include <string> + + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace { + +TEST(InvokeFlush, String) { + std::string str = "ABC"; + str_format_internal::InvokeFlush(&str, "DEF"); + EXPECT_EQ(str, "ABCDEF"); + +#if UTIL_FORMAT_HAS_GLOBAL_STRING + std::string str2 = "ABC"; + str_format_internal::InvokeFlush(&str2, "DEF"); + EXPECT_EQ(str2, "ABCDEF"); +#endif // UTIL_FORMAT_HAS_GLOBAL_STRING +} + +TEST(InvokeFlush, Stream) { + std::stringstream str; + str << "ABC"; + str_format_internal::InvokeFlush(&str, "DEF"); + EXPECT_EQ(str.str(), "ABCDEF"); +} + +TEST(BufferRawSink, Limits) { + char buf[16]; + { + std::fill(std::begin(buf), std::end(buf), 'x'); + str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1); + str_format_internal::InvokeFlush(&bufsink, "Hello World237"); + EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World237xx"); + } + { + std::fill(std::begin(buf), std::end(buf), 'x'); + str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1); + str_format_internal::InvokeFlush(&bufsink, "Hello World237237"); + EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World2372x"); + } + { + std::fill(std::begin(buf), std::end(buf), 'x'); + str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1); + str_format_internal::InvokeFlush(&bufsink, "Hello World"); + str_format_internal::InvokeFlush(&bufsink, "237"); + EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World237xx"); + } + { + std::fill(std::begin(buf), std::end(buf), 'x'); + str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1); + str_format_internal::InvokeFlush(&bufsink, "Hello World"); + str_format_internal::InvokeFlush(&bufsink, "237237"); + EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World2372x"); + } +} + +} // namespace +} // inline namespace lts_2018_12_18 +} // namespace absl + diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc new file mode 100644 index 00000000..119a711e --- /dev/null +++ b/absl/strings/internal/str_format/parser.cc @@ -0,0 +1,311 @@ +#include "absl/strings/internal/str_format/parser.h" + +#include <assert.h> +#include <string.h> +#include <wchar.h> +#include <cctype> +#include <cstdint> + +#include <algorithm> +#include <initializer_list> +#include <limits> +#include <ostream> +#include <string> +#include <unordered_set> + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { +namespace { + +bool CheckFastPathSetting(const UnboundConversion& conv) { + bool should_be_basic = !conv.flags.left && // + !conv.flags.show_pos && // + !conv.flags.sign_col && // + !conv.flags.alt && // + !conv.flags.zero && // + (conv.width.value() == -1) && + (conv.precision.value() == -1); + if (should_be_basic != conv.flags.basic) { + fprintf(stderr, + "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d " + "width=%d precision=%d\n", + conv.flags.basic, conv.flags.left, conv.flags.show_pos, + conv.flags.sign_col, conv.flags.alt, conv.flags.zero, + conv.width.value(), conv.precision.value()); + } + return should_be_basic == conv.flags.basic; +} + +// Keep a single table for all the conversion chars and length modifiers. +// We invert the length modifiers to make them negative so that we can easily +// test for them. +// Everything else is `none`, which is a negative constant. +using CC = ConversionChar::Id; +using LM = LengthMod::Id; +static constexpr std::int8_t none = -128; +static constexpr std::int8_t kIds[] = { + none, none, none, none, none, none, none, none, // 00-07 + none, none, none, none, none, none, none, none, // 08-0f + none, none, none, none, none, none, none, none, // 10-17 + none, none, none, none, none, none, none, none, // 18-1f + none, none, none, none, none, none, none, none, // 20-27 + none, none, none, none, none, none, none, none, // 28-2f + none, none, none, none, none, none, none, none, // 30-37 + none, none, none, none, none, none, none, none, // 38-3f + none, CC::A, none, CC::C, none, CC::E, CC::F, CC::G, // @ABCDEFG + none, none, none, none, ~LM::L, none, none, none, // HIJKLMNO + none, none, none, CC::S, none, none, none, none, // PQRSTUVW + CC::X, none, none, none, none, none, none, none, // XYZ[\]^_ + none, CC::a, none, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg + ~LM::h, CC::i, ~LM::j, none, ~LM::l, none, CC::n, CC::o, // hijklmno + CC::p, ~LM::q, none, CC::s, ~LM::t, CC::u, none, none, // pqrstuvw + CC::x, none, ~LM::z, none, none, none, none, none, // xyz{|}~! + none, none, none, none, none, none, none, none, // 80-87 + none, none, none, none, none, none, none, none, // 88-8f + none, none, none, none, none, none, none, none, // 90-97 + none, none, none, none, none, none, none, none, // 98-9f + none, none, none, none, none, none, none, none, // a0-a7 + none, none, none, none, none, none, none, none, // a8-af + none, none, none, none, none, none, none, none, // b0-b7 + none, none, none, none, none, none, none, none, // b8-bf + none, none, none, none, none, none, none, none, // c0-c7 + none, none, none, none, none, none, none, none, // c8-cf + none, none, none, none, none, none, none, none, // d0-d7 + none, none, none, none, none, none, none, none, // d8-df + none, none, none, none, none, none, none, none, // e0-e7 + none, none, none, none, none, none, none, none, // e8-ef + none, none, none, none, none, none, none, none, // f0-f7 + none, none, none, none, none, none, none, none, // f8-ff +}; + +template <bool is_positional> +bool ConsumeConversion(string_view *src, UnboundConversion *conv, + int *next_arg) { + const char *pos = src->data(); + const char *const end = pos + src->size(); + char c; + // Read the next char into `c` and update `pos`. Returns false if there are + // no more chars to read. +#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \ + do { \ + if (ABSL_PREDICT_FALSE(pos == end)) return false; \ + c = *pos++; \ + } while (0) + + const auto parse_digits = [&] { + int digits = c - '0'; + // We do not want to overflow `digits` so we consume at most digits10 + // digits. If there are more digits the parsing will fail later on when the + // digit doesn't match the expected characters. + int num_digits = std::numeric_limits<int>::digits10; + for (;;) { + if (ABSL_PREDICT_FALSE(pos == end || !num_digits)) break; + c = *pos++; + if (!std::isdigit(c)) break; + --num_digits; + digits = 10 * digits + c - '0'; + } + return digits; + }; + + if (is_positional) { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return false; + conv->arg_position = parse_digits(); + assert(conv->arg_position > 0); + if (ABSL_PREDICT_FALSE(c != '$')) return false; + } + + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + + // We should start with the basic flag on. + assert(conv->flags.basic); + + // Any non alpha character makes this conversion not basic. + // This includes flags (-+ #0), width (1-9, *) or precision (.). + // All conversion characters and length modifiers are alpha characters. + if (c < 'A') { + conv->flags.basic = false; + + for (; c <= '0';) { + // FIXME: We might be able to speed this up reusing the kIds lookup table + // from above. + // It might require changing Flags to be a plain integer where we can |= a + // value. + switch (c) { + case '-': + conv->flags.left = true; + break; + case '+': + conv->flags.show_pos = true; + break; + case ' ': + conv->flags.sign_col = true; + break; + case '#': + conv->flags.alt = true; + break; + case '0': + conv->flags.zero = true; + break; + default: + goto flags_done; + } + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } +flags_done: + + if (c <= '9') { + if (c >= '0') { + int maybe_width = parse_digits(); + if (!is_positional && c == '$') { + if (ABSL_PREDICT_FALSE(*next_arg != 0)) return false; + // Positional conversion. + *next_arg = -1; + conv->flags = Flags(); + conv->flags.basic = true; + return ConsumeConversion<true>(src, conv, next_arg); + } + conv->width.set_value(maybe_width); + } else if (c == '*') { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (is_positional) { + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return false; + conv->width.set_from_arg(parse_digits()); + if (ABSL_PREDICT_FALSE(c != '$')) return false; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->width.set_from_arg(++*next_arg); + } + } + } + + if (c == '.') { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (std::isdigit(c)) { + conv->precision.set_value(parse_digits()); + } else if (c == '*') { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (is_positional) { + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return false; + conv->precision.set_from_arg(parse_digits()); + if (c != '$') return false; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->precision.set_from_arg(++*next_arg); + } + } else { + conv->precision.set_value(0); + } + } + } + + std::int8_t id = kIds[static_cast<unsigned char>(c)]; + + if (id < 0) { + if (ABSL_PREDICT_FALSE(id == none)) return false; + + // It is a length modifier. + using str_format_internal::LengthMod; + LengthMod length_mod = LengthMod::FromId(static_cast<LM>(~id)); + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (c == 'h' && length_mod.id() == LengthMod::h) { + conv->length_mod = LengthMod::FromId(LengthMod::hh); + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else if (c == 'l' && length_mod.id() == LengthMod::l) { + conv->length_mod = LengthMod::FromId(LengthMod::ll); + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->length_mod = length_mod; + } + id = kIds[static_cast<unsigned char>(c)]; + if (ABSL_PREDICT_FALSE(id < 0)) return false; + } + + assert(CheckFastPathSetting(*conv)); + (void)(&CheckFastPathSetting); + + conv->conv = ConversionChar::FromId(static_cast<CC>(id)); + if (!is_positional) conv->arg_position = ++*next_arg; + *src = string_view(pos, end - pos); + return true; +} + +} // namespace + +bool ConsumeUnboundConversion(string_view *src, UnboundConversion *conv, + int *next_arg) { + if (*next_arg < 0) return ConsumeConversion<true>(src, conv, next_arg); + return ConsumeConversion<false>(src, conv, next_arg); +} + +struct ParsedFormatBase::ParsedFormatConsumer { + explicit ParsedFormatConsumer(ParsedFormatBase *parsedformat) + : parsed(parsedformat), data_pos(parsedformat->data_.get()) {} + + bool Append(string_view s) { + if (s.empty()) return true; + + size_t text_end = AppendText(s); + + if (!parsed->items_.empty() && !parsed->items_.back().is_conversion) { + // Let's extend the existing text run. + parsed->items_.back().text_end = text_end; + } else { + // Let's make a new text run. + parsed->items_.push_back({false, text_end, {}}); + } + return true; + } + + bool ConvertOne(const UnboundConversion &conv, string_view s) { + size_t text_end = AppendText(s); + parsed->items_.push_back({true, text_end, conv}); + return true; + } + + size_t AppendText(string_view s) { + memcpy(data_pos, s.data(), s.size()); + data_pos += s.size(); + return static_cast<size_t>(data_pos - parsed->data_.get()); + } + + ParsedFormatBase *parsed; + char* data_pos; +}; + +ParsedFormatBase::ParsedFormatBase(string_view format, bool allow_ignored, + std::initializer_list<Conv> convs) + : data_(format.empty() ? nullptr : new char[format.size()]) { + has_error_ = !ParseFormatString(format, ParsedFormatConsumer(this)) || + !MatchesConversions(allow_ignored, convs); +} + +bool ParsedFormatBase::MatchesConversions( + bool allow_ignored, std::initializer_list<Conv> convs) const { + std::unordered_set<int> used; + auto add_if_valid_conv = [&](int pos, char c) { + if (static_cast<size_t>(pos) > convs.size() || + !Contains(convs.begin()[pos - 1], c)) + return false; + used.insert(pos); + return true; + }; + for (const ConversionItem &item : items_) { + if (!item.is_conversion) continue; + auto &conv = item.conv; + if (conv.precision.is_from_arg() && + !add_if_valid_conv(conv.precision.get_from_arg(), '*')) + return false; + if (conv.width.is_from_arg() && + !add_if_valid_conv(conv.width.get_from_arg(), '*')) + return false; + if (!add_if_valid_conv(conv.arg_position, conv.conv.Char())) return false; + } + return used.size() == convs.size() || allow_ignored; +} + +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h new file mode 100644 index 00000000..9842c117 --- /dev/null +++ b/absl/strings/internal/str_format/parser.h @@ -0,0 +1,294 @@ +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ + +#include <limits.h> +#include <stddef.h> +#include <stdlib.h> + +#include <cassert> +#include <initializer_list> +#include <iosfwd> +#include <iterator> +#include <memory> +#include <vector> + +#include "absl/strings/internal/str_format/checker.h" +#include "absl/strings/internal/str_format/extension.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { + +// The analyzed properties of a single specified conversion. +struct UnboundConversion { + UnboundConversion() + : flags() /* This is required to zero all the fields of flags. */ { + flags.basic = true; + } + + class InputValue { + public: + void set_value(int value) { + assert(value >= 0); + value_ = value; + } + int value() const { return value_; } + + // Marks the value as "from arg". aka the '*' format. + // Requires `value >= 1`. + // When set, is_from_arg() return true and get_from_arg() returns the + // original value. + // `value()`'s return value is unspecfied in this state. + void set_from_arg(int value) { + assert(value > 0); + value_ = -value - 1; + } + bool is_from_arg() const { return value_ < -1; } + int get_from_arg() const { + assert(is_from_arg()); + return -value_ - 1; + } + + private: + int value_ = -1; + }; + + // No need to initialize. It will always be set in the parser. + int arg_position; + + InputValue width; + InputValue precision; + + Flags flags; + LengthMod length_mod; + ConversionChar conv; +}; + +// Consume conversion spec prefix (not including '%') of '*src' if valid. +// Examples of valid specs would be e.g.: "s", "d", "-12.6f". +// If valid, the front of src is advanced such that src becomes the +// part following the conversion spec, and the spec part is broken down and +// returned in 'conv'. +// If invalid, returns false and leaves 'src' unmodified. +// For example: +// Given "d9", returns "d", and leaves src="9", +// Given "!", returns "" and leaves src="!". +bool ConsumeUnboundConversion(string_view* src, UnboundConversion* conv, + int* next_arg); + +// Parse the format string provided in 'src' and pass the identified items into +// 'consumer'. +// Text runs will be passed by calling +// Consumer::Append(string_view); +// ConversionItems will be passed by calling +// Consumer::ConvertOne(UnboundConversion, string_view); +// In the case of ConvertOne, the string_view that is passed is the +// portion of the format string corresponding to the conversion, not including +// the leading %. On success, it returns true. On failure, it stops and returns +// false. +template <typename Consumer> +bool ParseFormatString(string_view src, Consumer consumer) { + int next_arg = 0; + while (!src.empty()) { + const char* percent = + static_cast<const char*>(memchr(src.data(), '%', src.size())); + if (!percent) { + // We found the last substring. + return consumer.Append(src); + } + // We found a percent, so push the text run then process the percent. + size_t percent_loc = percent - src.data(); + if (!consumer.Append(string_view(src.data(), percent_loc))) return false; + if (percent + 1 >= src.data() + src.size()) return false; + + UnboundConversion conv; + + switch (percent[1]) { + case '%': + if (!consumer.Append("%")) return false; + src.remove_prefix(percent_loc + 2); + continue; + +#define PARSER_CASE(ch) \ + case #ch[0]: \ + src.remove_prefix(percent_loc + 2); \ + conv.conv = ConversionChar::FromId(ConversionChar::ch); \ + conv.arg_position = ++next_arg; \ + break; + ABSL_CONVERSION_CHARS_EXPAND_(PARSER_CASE, ); +#undef PARSER_CASE + + default: + src.remove_prefix(percent_loc + 1); + if (!ConsumeUnboundConversion(&src, &conv, &next_arg)) return false; + break; + } + if (next_arg == 0) { + // This indicates an error in the format std::string. + // The only way to get next_arg == 0 is to have a positional argument + // first which sets next_arg to -1 and then a non-positional argument + // which does ++next_arg. + // Checking here seems to be the cheapeast place to do it. + return false; + } + if (!consumer.ConvertOne( + conv, string_view(percent + 1, src.data() - (percent + 1)))) { + return false; + } + } + return true; +} + +// Always returns true, or fails to compile in a constexpr context if s does not +// point to a constexpr char array. +constexpr bool EnsureConstexpr(string_view s) { + return s.empty() || s[0] == s[0]; +} + +class ParsedFormatBase { + public: + explicit ParsedFormatBase(string_view format, bool allow_ignored, + std::initializer_list<Conv> convs); + + ParsedFormatBase(const ParsedFormatBase& other) { *this = other; } + + ParsedFormatBase(ParsedFormatBase&& other) { *this = std::move(other); } + + ParsedFormatBase& operator=(const ParsedFormatBase& other) { + if (this == &other) return *this; + has_error_ = other.has_error_; + items_ = other.items_; + size_t text_size = items_.empty() ? 0 : items_.back().text_end; + data_.reset(new char[text_size]); + memcpy(data_.get(), other.data_.get(), text_size); + return *this; + } + + ParsedFormatBase& operator=(ParsedFormatBase&& other) { + if (this == &other) return *this; + has_error_ = other.has_error_; + data_ = std::move(other.data_); + items_ = std::move(other.items_); + // Reset the vector to make sure the invariants hold. + other.items_.clear(); + return *this; + } + + template <typename Consumer> + bool ProcessFormat(Consumer consumer) const { + const char* const base = data_.get(); + string_view text(base, 0); + for (const auto& item : items_) { + const char* const end = text.data() + text.size(); + text = string_view(end, (base + item.text_end) - end); + if (item.is_conversion) { + if (!consumer.ConvertOne(item.conv, text)) return false; + } else { + if (!consumer.Append(text)) return false; + } + } + return !has_error_; + } + + bool has_error() const { return has_error_; } + + private: + // Returns whether the conversions match and if !allow_ignored it verifies + // that all conversions are used by the format. + bool MatchesConversions(bool allow_ignored, + std::initializer_list<Conv> convs) const; + + struct ParsedFormatConsumer; + + struct ConversionItem { + bool is_conversion; + // Points to the past-the-end location of this element in the data_ array. + size_t text_end; + UnboundConversion conv; + }; + + bool has_error_; + std::unique_ptr<char[]> data_; + std::vector<ConversionItem> items_; +}; + + +// A value type representing a preparsed format. These can be created, copied +// around, and reused to speed up formatting loops. +// The user must specify through the template arguments the conversion +// characters used in the format. This will be checked at compile time. +// +// This class uses Conv enum values to specify each argument. +// This allows for more flexibility as you can specify multiple possible +// conversion characters for each argument. +// ParsedFormat<char...> is a simplified alias for when the user only +// needs to specify a single conversion character for each argument. +// +// Example: +// // Extended format supports multiple characters per argument: +// using MyFormat = ExtendedParsedFormat<Conv::d | Conv::x>; +// MyFormat GetFormat(bool use_hex) { +// if (use_hex) return MyFormat("foo %x bar"); +// return MyFormat("foo %d bar"); +// } +// // 'format' can be used with any value that supports 'd' and 'x', +// // like `int`. +// auto format = GetFormat(use_hex); +// value = StringF(format, i); +// +// This class also supports runtime format checking with the ::New() and +// ::NewAllowIgnored() factory functions. +// This is the only API that allows the user to pass a runtime specified format +// string. These factory functions will return NULL if the format does not match +// the conversions requested by the user. +template <str_format_internal::Conv... C> +class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase { + public: + explicit ExtendedParsedFormat(string_view format) +#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + __attribute__(( + enable_if(str_format_internal::EnsureConstexpr(format), + "Format std::string is not constexpr."), + enable_if(str_format_internal::ValidFormatImpl<C...>(format), + "Format specified does not match the template arguments."))) +#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER + : ExtendedParsedFormat(format, false) { + } + + // ExtendedParsedFormat factory function. + // The user still has to specify the conversion characters, but they will not + // be checked at compile time. Instead, it will be checked at runtime. + // This delays the checking to runtime, but allows the user to pass + // dynamically sourced formats. + // It returns NULL if the format does not match the conversion characters. + // The user is responsible for checking the return value before using it. + // + // The 'New' variant will check that all the specified arguments are being + // consumed by the format and return NULL if any argument is being ignored. + // The 'NewAllowIgnored' variant will not verify this and will allow formats + // that ignore arguments. + static std::unique_ptr<ExtendedParsedFormat> New(string_view format) { + return New(format, false); + } + static std::unique_ptr<ExtendedParsedFormat> NewAllowIgnored( + string_view format) { + return New(format, true); + } + + private: + static std::unique_ptr<ExtendedParsedFormat> New(string_view format, + bool allow_ignored) { + std::unique_ptr<ExtendedParsedFormat> conv( + new ExtendedParsedFormat(format, allow_ignored)); + if (conv->has_error()) return nullptr; + return conv; + } + + ExtendedParsedFormat(string_view s, bool allow_ignored) + : ParsedFormatBase(s, allow_ignored, {C...}) {} +}; +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc new file mode 100644 index 00000000..14d90344 --- /dev/null +++ b/absl/strings/internal/str_format/parser_test.cc @@ -0,0 +1,391 @@ +#include "absl/strings/internal/str_format/parser.h" + +#include <string.h> +#include "gtest/gtest.h" +#include "absl/base/macros.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace str_format_internal { + +namespace { + +TEST(LengthModTest, Names) { + struct Expectation { + int line; + LengthMod::Id id; + const char *name; + }; + const Expectation kExpect[] = { + {__LINE__, LengthMod::none, "" }, + {__LINE__, LengthMod::h, "h" }, + {__LINE__, LengthMod::hh, "hh"}, + {__LINE__, LengthMod::l, "l" }, + {__LINE__, LengthMod::ll, "ll"}, + {__LINE__, LengthMod::L, "L" }, + {__LINE__, LengthMod::j, "j" }, + {__LINE__, LengthMod::z, "z" }, + {__LINE__, LengthMod::t, "t" }, + {__LINE__, LengthMod::q, "q" }, + }; + EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), LengthMod::kNumValues); + for (auto e : kExpect) { + SCOPED_TRACE(e.line); + LengthMod mod = LengthMod::FromId(e.id); + EXPECT_EQ(e.id, mod.id()); + EXPECT_EQ(e.name, mod.name()); + } +} + +TEST(ConversionCharTest, Names) { + struct Expectation { + ConversionChar::Id id; + char name; + }; + // clang-format off + const Expectation kExpect[] = { +#define X(c) {ConversionChar::c, #c[0]} + X(c), X(C), X(s), X(S), // text + X(d), X(i), X(o), X(u), X(x), X(X), // int + X(f), X(F), X(e), X(E), X(g), X(G), X(a), X(A), // float + X(n), X(p), // misc +#undef X + {ConversionChar::none, '\0'}, + }; + // clang-format on + EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), ConversionChar::kNumValues); + for (auto e : kExpect) { + SCOPED_TRACE(e.name); + ConversionChar v = ConversionChar::FromId(e.id); + EXPECT_EQ(e.id, v.id()); + EXPECT_EQ(e.name, v.Char()); + } +} + +class ConsumeUnboundConversionTest : public ::testing::Test { + public: + typedef UnboundConversion Props; + string_view Consume(string_view* src) { + int next = 0; + const char* prev_begin = src->data(); + o = UnboundConversion(); // refresh + ConsumeUnboundConversion(src, &o, &next); + return {prev_begin, static_cast<size_t>(src->data() - prev_begin)}; + } + + bool Run(const char *fmt, bool force_positional = false) { + string_view src = fmt; + int next = force_positional ? -1 : 0; + o = UnboundConversion(); // refresh + return ConsumeUnboundConversion(&src, &o, &next) && src.empty(); + } + UnboundConversion o; +}; + +TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) { + struct Expectation { + int line; + string_view src; + string_view out; + string_view src_post; + }; + const Expectation kExpect[] = { + {__LINE__, "", "", "" }, + {__LINE__, "b", "", "b" }, // 'b' is invalid + {__LINE__, "ba", "", "ba"}, // 'b' is invalid + {__LINE__, "l", "", "l" }, // just length mod isn't okay + {__LINE__, "d", "d", "" }, // basic + {__LINE__, "d ", "d", " " }, // leave suffix + {__LINE__, "dd", "d", "d" }, // don't be greedy + {__LINE__, "d9", "d", "9" }, // leave non-space suffix + {__LINE__, "dzz", "d", "zz"}, // length mod as suffix + {__LINE__, "1$*2$d", "1$*2$d", "" }, // arg indexing and * allowed. + {__LINE__, "0-14.3hhd", "0-14.3hhd", ""}, // precision, width + {__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""}, // flags + }; + for (const auto& e : kExpect) { + SCOPED_TRACE(e.line); + string_view src = e.src; + EXPECT_EQ(e.src, src); + string_view out = Consume(&src); + EXPECT_EQ(e.out, out); + EXPECT_EQ(e.src_post, src); + } +} + +TEST_F(ConsumeUnboundConversionTest, BasicConversion) { + EXPECT_FALSE(Run("")); + EXPECT_FALSE(Run("z")); + + EXPECT_FALSE(Run("dd")); // no excess allowed + + EXPECT_TRUE(Run("d")); + EXPECT_EQ('d', o.conv.Char()); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_LT(o.width.value(), 0); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_LT(o.precision.value(), 0); + EXPECT_EQ(1, o.arg_position); + EXPECT_EQ(LengthMod::none, o.length_mod.id()); +} + +TEST_F(ConsumeUnboundConversionTest, ArgPosition) { + EXPECT_TRUE(Run("d")); + EXPECT_EQ(1, o.arg_position); + EXPECT_TRUE(Run("3$d")); + EXPECT_EQ(3, o.arg_position); + EXPECT_TRUE(Run("1$d")); + EXPECT_EQ(1, o.arg_position); + EXPECT_TRUE(Run("1$d", true)); + EXPECT_EQ(1, o.arg_position); + EXPECT_TRUE(Run("123$d")); + EXPECT_EQ(123, o.arg_position); + EXPECT_TRUE(Run("123$d", true)); + EXPECT_EQ(123, o.arg_position); + EXPECT_TRUE(Run("10$d")); + EXPECT_EQ(10, o.arg_position); + EXPECT_TRUE(Run("10$d", true)); + EXPECT_EQ(10, o.arg_position); + + // Position can't be zero. + EXPECT_FALSE(Run("0$d")); + EXPECT_FALSE(Run("0$d", true)); + EXPECT_FALSE(Run("1$*0$d")); + EXPECT_FALSE(Run("1$.*0$d")); + + // Position can't start with a zero digit at all. That is not a 'decimal'. + EXPECT_FALSE(Run("01$p")); + EXPECT_FALSE(Run("01$p", true)); + EXPECT_FALSE(Run("1$*01$p")); + EXPECT_FALSE(Run("1$.*01$p")); +} + +TEST_F(ConsumeUnboundConversionTest, WidthAndPrecision) { + EXPECT_TRUE(Run("14d")); + EXPECT_EQ('d', o.conv.Char()); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_EQ(14, o.width.value()); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_LT(o.precision.value(), 0); + + EXPECT_TRUE(Run("14.d")); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_EQ(14, o.width.value()); + EXPECT_EQ(0, o.precision.value()); + + EXPECT_TRUE(Run(".d")); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_LT(o.width.value(), 0); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_EQ(0, o.precision.value()); + + EXPECT_TRUE(Run(".5d")); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_LT(o.width.value(), 0); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_EQ(5, o.precision.value()); + + EXPECT_TRUE(Run(".0d")); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_LT(o.width.value(), 0); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_EQ(0, o.precision.value()); + + EXPECT_TRUE(Run("14.5d")); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_EQ(14, o.width.value()); + EXPECT_EQ(5, o.precision.value()); + + EXPECT_TRUE(Run("*.*d")); + EXPECT_TRUE(o.width.is_from_arg()); + EXPECT_EQ(1, o.width.get_from_arg()); + EXPECT_TRUE(o.precision.is_from_arg()); + EXPECT_EQ(2, o.precision.get_from_arg()); + EXPECT_EQ(3, o.arg_position); + + EXPECT_TRUE(Run("*d")); + EXPECT_TRUE(o.width.is_from_arg()); + EXPECT_EQ(1, o.width.get_from_arg()); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_LT(o.precision.value(), 0); + EXPECT_EQ(2, o.arg_position); + + EXPECT_TRUE(Run(".*d")); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_LT(o.width.value(), 0); + EXPECT_TRUE(o.precision.is_from_arg()); + EXPECT_EQ(1, o.precision.get_from_arg()); + EXPECT_EQ(2, o.arg_position); + + // mixed implicit and explicit: didn't specify arg position. + EXPECT_FALSE(Run("*23$.*34$d")); + + EXPECT_TRUE(Run("12$*23$.*34$d")); + EXPECT_EQ(12, o.arg_position); + EXPECT_TRUE(o.width.is_from_arg()); + EXPECT_EQ(23, o.width.get_from_arg()); + EXPECT_TRUE(o.precision.is_from_arg()); + EXPECT_EQ(34, o.precision.get_from_arg()); + + EXPECT_TRUE(Run("2$*5$.*9$d")); + EXPECT_EQ(2, o.arg_position); + EXPECT_TRUE(o.width.is_from_arg()); + EXPECT_EQ(5, o.width.get_from_arg()); + EXPECT_TRUE(o.precision.is_from_arg()); + EXPECT_EQ(9, o.precision.get_from_arg()); + + EXPECT_FALSE(Run(".*0$d")) << "no arg 0"; + + // Large values + EXPECT_TRUE(Run("999999999.999999999d")); + EXPECT_FALSE(o.width.is_from_arg()); + EXPECT_EQ(999999999, o.width.value()); + EXPECT_FALSE(o.precision.is_from_arg()); + EXPECT_EQ(999999999, o.precision.value()); + + EXPECT_FALSE(Run("1000000000.999999999d")); + EXPECT_FALSE(Run("999999999.1000000000d")); +} + +TEST_F(ConsumeUnboundConversionTest, Flags) { + static const char kAllFlags[] = "-+ #0"; + static const int kNumFlags = ABSL_ARRAYSIZE(kAllFlags) - 1; + for (int rev = 0; rev < 2; ++rev) { + for (int i = 0; i < 1 << kNumFlags; ++i) { + std::string fmt; + for (int k = 0; k < kNumFlags; ++k) + if ((i >> k) & 1) fmt += kAllFlags[k]; + // flag order shouldn't matter + if (rev == 1) { std::reverse(fmt.begin(), fmt.end()); } + fmt += 'd'; + SCOPED_TRACE(fmt); + EXPECT_TRUE(Run(fmt.c_str())); + EXPECT_EQ(fmt.find('-') == std::string::npos, !o.flags.left); + EXPECT_EQ(fmt.find('+') == std::string::npos, !o.flags.show_pos); + EXPECT_EQ(fmt.find(' ') == std::string::npos, !o.flags.sign_col); + EXPECT_EQ(fmt.find('#') == std::string::npos, !o.flags.alt); + EXPECT_EQ(fmt.find('0') == std::string::npos, !o.flags.zero); + } + } +} + +TEST_F(ConsumeUnboundConversionTest, BasicFlag) { + // Flag is on + for (const char* fmt : {"d", "llx", "G", "1$X"}) { + SCOPED_TRACE(fmt); + EXPECT_TRUE(Run(fmt)); + EXPECT_TRUE(o.flags.basic); + } + + // Flag is off + for (const char* fmt : {"3d", ".llx", "-G", "1$#X"}) { + SCOPED_TRACE(fmt); + EXPECT_TRUE(Run(fmt)); + EXPECT_FALSE(o.flags.basic); + } +} + +struct SummarizeConsumer { + std::string* out; + explicit SummarizeConsumer(std::string* out) : out(out) {} + + bool Append(string_view s) { + *out += "[" + std::string(s) + "]"; + return true; + } + + bool ConvertOne(const UnboundConversion& conv, string_view s) { + *out += "{"; + *out += std::string(s); + *out += ":"; + *out += std::to_string(conv.arg_position) + "$"; + if (conv.width.is_from_arg()) { + *out += std::to_string(conv.width.get_from_arg()) + "$*"; + } + if (conv.precision.is_from_arg()) { + *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*"; + } + *out += conv.conv.Char(); + *out += "}"; + return true; + } +}; + +std::string SummarizeParsedFormat(const ParsedFormatBase& pc) { + std::string out; + if (!pc.ProcessFormat(SummarizeConsumer(&out))) out += "!"; + return out; +} + +class ParsedFormatTest : public testing::Test {}; + +TEST_F(ParsedFormatTest, ValueSemantics) { + ParsedFormatBase p1({}, true, {}); // empty format + EXPECT_EQ("", SummarizeParsedFormat(p1)); + + ParsedFormatBase p2 = p1; // copy construct (empty) + EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2)); + + p1 = ParsedFormatBase("hello%s", true, {Conv::s}); // move assign + EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p1)); + + ParsedFormatBase p3 = p1; // copy construct (nonempty) + EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p3)); + + using std::swap; + swap(p1, p2); + EXPECT_EQ("", SummarizeParsedFormat(p1)); + EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p2)); + swap(p1, p2); // undo + + p2 = p1; // copy assign + EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2)); +} + +struct ExpectParse { + const char* in; + std::initializer_list<Conv> conv_set; + const char* out; +}; + +TEST_F(ParsedFormatTest, Parsing) { + // Parse should be equivalent to that obtained by ConversionParseIterator. + // No need to retest the parsing edge cases here. + const ExpectParse kExpect[] = { + {"", {}, ""}, + {"ab", {}, "[ab]"}, + {"a%d", {Conv::d}, "[a]{d:1$d}"}, + {"a%+d", {Conv::d}, "[a]{+d:1$d}"}, + {"a% d", {Conv::d}, "[a]{ d:1$d}"}, + {"a%b %d", {}, "[a]!"}, // stop after error + }; + for (const auto& e : kExpect) { + SCOPED_TRACE(e.in); + EXPECT_EQ(e.out, + SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set))); + } +} + +TEST_F(ParsedFormatTest, ParsingFlagOrder) { + const ExpectParse kExpect[] = { + {"a%+ 0d", {Conv::d}, "[a]{+ 0d:1$d}"}, + {"a%+0 d", {Conv::d}, "[a]{+0 d:1$d}"}, + {"a%0+ d", {Conv::d}, "[a]{0+ d:1$d}"}, + {"a% +0d", {Conv::d}, "[a]{ +0d:1$d}"}, + {"a%0 +d", {Conv::d}, "[a]{0 +d:1$d}"}, + {"a% 0+d", {Conv::d}, "[a]{ 0+d:1$d}"}, + {"a%+ 0+d", {Conv::d}, "[a]{+ 0+d:1$d}"}, + }; + for (const auto& e : kExpect) { + SCOPED_TRACE(e.in); + EXPECT_EQ(e.out, + SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set))); + } +} + +} // namespace +} // namespace str_format_internal +} // inline namespace lts_2018_12_18 +} // namespace absl diff --git a/absl/strings/internal/str_join_internal.h b/absl/strings/internal/str_join_internal.h index 5834403e..90b06d6f 100644 --- a/absl/strings/internal/str_join_internal.h +++ b/absl/strings/internal/str_join_internal.h @@ -43,7 +43,7 @@ #include "absl/strings/str_cat.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { // @@ -190,7 +190,7 @@ struct DefaultFormatter<std::unique_ptr<ValueType>> // // The main joining algorithm. This simply joins the elements in the given -// iterator range, each separated by the given separator, into an output std::string, +// iterator range, each separated by the given separator, into an output string, // and formats each element using the provided Formatter object. template <typename Iterator, typename Formatter> std::string JoinAlgorithm(Iterator start, Iterator end, absl::string_view s, @@ -206,20 +206,20 @@ std::string JoinAlgorithm(Iterator start, Iterator end, absl::string_view s, } // A joining algorithm that's optimized for a forward iterator range of -// std::string-like objects that do not need any additional formatting. This is to -// optimize the common case of joining, say, a std::vector<std::string> or a +// string-like objects that do not need any additional formatting. This is to +// optimize the common case of joining, say, a std::vector<string> or a // std::vector<absl::string_view>. // // This is an overload of the previous JoinAlgorithm() function. Here the // Formatter argument is of type NoFormatter. Since NoFormatter is an internal // type, this overload is only invoked when strings::Join() is called with a -// range of std::string-like objects (e.g., std::string, absl::string_view), and an +// range of string-like objects (e.g., string, absl::string_view), and an // explicit Formatter argument was NOT specified. // // The optimization is that the needed space will be reserved in the output -// std::string to avoid the need to resize while appending. To do this, the iterator +// string to avoid the need to resize while appending. To do this, the iterator // range will be traversed twice: once to calculate the total needed size, and -// then again to copy the elements and delimiters to the output std::string. +// then again to copy the elements and delimiters to the output string. template <typename Iterator, typename = typename std::enable_if<std::is_convertible< typename std::iterator_traits<Iterator>::iterator_category, @@ -307,7 +307,7 @@ std::string JoinRange(const Range& range, absl::string_view separator) { } } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_STR_JOIN_INTERNAL_H_ diff --git a/absl/strings/internal/str_split_internal.h b/absl/strings/internal/str_split_internal.h index e2d2c6b0..2300193a 100644 --- a/absl/strings/internal/str_split_internal.h +++ b/absl/strings/internal/str_split_internal.h @@ -47,12 +47,12 @@ #endif // _GLIBCXX_DEBUG namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { // This class is implicitly constructible from everything that absl::string_view // is implicitly constructible from. If it's constructed from a temporary -// std::string, the data is moved into a data member so its lifetime matches that of +// string, the data is moved into a data member so its lifetime matches that of // the ConvertibleToStringView instance. class ConvertibleToStringView { public: @@ -103,7 +103,7 @@ ConvertibleToStringView(std::string&& s) // NOLINT(runtime/explicit) absl::string_view value_; }; -// An iterator that enumerates the parts of a std::string from a Splitter. The text +// An iterator that enumerates the parts of a string from a Splitter. The text // to be split, the Delimiter, and the Predicate are all taken from the given // Splitter object. Iterators may only be compared if they refer to the same // Splitter instance. @@ -160,7 +160,7 @@ class SplitIterator { } const absl::string_view text = splitter_->text(); const absl::string_view d = delimiter_.Find(text, pos_); - if (d.data() == text.end()) state_ = kLastState; + if (d.data() == text.data() + text.size()) state_ = kLastState; curr_ = text.substr(pos_, d.data() - (text.data() + pos_)); pos_ += curr_.size() + d.size(); } while (!predicate_(curr_)); @@ -229,14 +229,31 @@ struct IsInitializerList // compiled in C++11 will get an error due to ambiguous conversion paths (in // C++11 std::vector<T>::operator= is overloaded to take either a std::vector<T> // or an std::initializer_list<T>). + +template <typename C, bool has_value_type, bool has_mapped_type> +struct SplitterIsConvertibleToImpl : std::false_type {}; + +template <typename C> +struct SplitterIsConvertibleToImpl<C, true, false> + : std::is_constructible<typename C::value_type, absl::string_view> {}; + +template <typename C> +struct SplitterIsConvertibleToImpl<C, true, true> + : absl::conjunction< + std::is_constructible<typename C::key_type, absl::string_view>, + std::is_constructible<typename C::mapped_type, absl::string_view>> {}; + template <typename C> struct SplitterIsConvertibleTo - : std::enable_if< + : SplitterIsConvertibleToImpl< + C, #ifdef _GLIBCXX_DEBUG !IsStrictlyBaseOfAndConvertibleToSTLContainer<C>::value && #endif // _GLIBCXX_DEBUG - !IsInitializerList<C>::value && HasValueType<C>::value && - HasConstIterator<C>::value> { + !IsInitializerList< + typename std::remove_reference<C>::type>::value && + HasValueType<C>::value && HasConstIterator<C>::value, + HasMappedType<C>::value> { }; // This class implements the range that is returned by absl::StrSplit(). This @@ -282,7 +299,8 @@ class Splitter { // An implicit conversion operator that is restricted to only those containers // that the splitter is convertible to. template <typename Container, - typename OnlyIf = typename SplitterIsConvertibleTo<Container>::type> + typename = typename std::enable_if< + SplitterIsConvertibleTo<Container>::value>::type> operator Container() const { // NOLINT(runtime/explicit) return ConvertToContainer<Container, typename Container::value_type, HasMappedType<Container>::value>()(*this); @@ -431,7 +449,7 @@ class Splitter { }; } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_STR_SPLIT_INTERNAL_H_ diff --git a/absl/strings/internal/utf8.cc b/absl/strings/internal/utf8.cc index 81b6241b..c6ab0d52 100644 --- a/absl/strings/internal/utf8.cc +++ b/absl/strings/internal/utf8.cc @@ -17,7 +17,7 @@ #include "absl/strings/internal/utf8.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { size_t EncodeUTF8Char(char *buffer, char32_t utf8_char) { @@ -49,5 +49,5 @@ size_t EncodeUTF8Char(char *buffer, char32_t utf8_char) { } } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/internal/utf8.h b/absl/strings/internal/utf8.h index 5a94ae6f..59c4f5b4 100644 --- a/absl/strings/internal/utf8.h +++ b/absl/strings/internal/utf8.h @@ -22,7 +22,7 @@ #include <cstdint> namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { // For Unicode code points 0 through 0x10FFFF, EncodeUTF8Char writes @@ -43,7 +43,7 @@ enum { kMaxEncodedUTF8Size = 4 }; size_t EncodeUTF8Char(char *buffer, char32_t utf8_char); } // namespace strings_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_INTERNAL_UTF8_H_ diff --git a/absl/strings/match.cc b/absl/strings/match.cc index 8f33c72c..12ba8edf 100644 --- a/absl/strings/match.cc +++ b/absl/strings/match.cc @@ -17,7 +17,7 @@ #include "absl/strings/internal/memutil.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace { bool CaseEqual(absl::string_view piece1, absl::string_view piece2) { @@ -28,6 +28,13 @@ bool CaseEqual(absl::string_view piece1, absl::string_view piece2) { } } // namespace +bool EqualsIgnoreCase(absl::string_view piece1, absl::string_view piece2) { + return (piece1.size() == piece2.size() && + 0 == absl::strings_internal::memcasecmp(piece1.data(), piece2.data(), + piece1.size())); + // memcasecmp uses absl::ascii_tolower(). +} + bool StartsWithIgnoreCase(absl::string_view text, absl::string_view prefix) { return (text.size() >= prefix.size()) && CaseEqual(text.substr(0, prefix.size()), prefix); @@ -38,5 +45,5 @@ bool EndsWithIgnoreCase(absl::string_view text, absl::string_view suffix) { CaseEqual(text.substr(text.size() - suffix.size()), suffix); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/match.h b/absl/strings/match.h index 908f051f..5805207c 100644 --- a/absl/strings/match.h +++ b/absl/strings/match.h @@ -17,7 +17,7 @@ // File: match.h // ----------------------------------------------------------------------------- // -// This file contains simple utilities for performing std::string matching checks. +// This file contains simple utilities for performing string matching checks. // All of these function parameters are specified as `absl::string_view`, // meaning that these functions can accept `std::string`, `absl::string_view` or // nul-terminated C-style strings. @@ -38,18 +38,18 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // StrContains() // -// Returns whether a given std::string `haystack` contains the substring `needle`. +// Returns whether a given string `haystack` contains the substring `needle`. inline bool StrContains(absl::string_view haystack, absl::string_view needle) { return haystack.find(needle, 0) != haystack.npos; } // StartsWith() // -// Returns whether a given std::string `text` begins with `prefix`. +// Returns whether a given string `text` begins with `prefix`. inline bool StartsWith(absl::string_view text, absl::string_view prefix) { return prefix.empty() || (text.size() >= prefix.size() && @@ -58,7 +58,7 @@ inline bool StartsWith(absl::string_view text, absl::string_view prefix) { // EndsWith() // -// Returns whether a given std::string `text` ends with `suffix`. +// Returns whether a given string `text` ends with `suffix`. inline bool EndsWith(absl::string_view text, absl::string_view suffix) { return suffix.empty() || (text.size() >= suffix.size() && @@ -67,19 +67,25 @@ inline bool EndsWith(absl::string_view text, absl::string_view suffix) { ); } -// StartsWithIgnoreCase() +// EqualsIgnoreCase() // -// Returns whether a given std::string `text` starts with `starts_with`, ignoring +// Returns whether given ASCII strings `piece1` and `piece2` are equal, ignoring // case in the comparison. +bool EqualsIgnoreCase(absl::string_view piece1, absl::string_view piece2); + +// StartsWithIgnoreCase() +// +// Returns whether a given ASCII string `text` starts with `starts_with`, +// ignoring case in the comparison. bool StartsWithIgnoreCase(absl::string_view text, absl::string_view prefix); // EndsWithIgnoreCase() // -// Returns whether a given std::string `text` ends with `ends_with`, ignoring case -// in the comparison. +// Returns whether a given ASCII string `text` ends with `ends_with`, ignoring +// case in the comparison. bool EndsWithIgnoreCase(absl::string_view text, absl::string_view suffix); -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_MATCH_H_ diff --git a/absl/strings/match_test.cc b/absl/strings/match_test.cc index d194f0e6..c21e00bf 100644 --- a/absl/strings/match_test.cc +++ b/absl/strings/match_test.cc @@ -80,6 +80,17 @@ TEST(MatchTest, ContainsNull) { EXPECT_FALSE(absl::StrContains(cs, sv2)); } +TEST(MatchTest, EqualsIgnoreCase) { + std::string text = "the"; + absl::string_view data(text); + + EXPECT_TRUE(absl::EqualsIgnoreCase(data, "The")); + EXPECT_TRUE(absl::EqualsIgnoreCase(data, "THE")); + EXPECT_TRUE(absl::EqualsIgnoreCase(data, "the")); + EXPECT_FALSE(absl::EqualsIgnoreCase(data, "Quick")); + EXPECT_FALSE(absl::EqualsIgnoreCase(data, "then")); +} + TEST(MatchTest, StartsWithIgnoreCase) { EXPECT_TRUE(absl::StartsWithIgnoreCase("foo", "foo")); EXPECT_TRUE(absl::StartsWithIgnoreCase("foo", "Fo")); diff --git a/absl/strings/numbers.cc b/absl/strings/numbers.cc index 48dca919..4c3ddb34 100644 --- a/absl/strings/numbers.cc +++ b/absl/strings/numbers.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file contains std::string processing functions related to +// This file contains string processing functions related to // numeric values. #include "absl/strings/numbers.h" @@ -30,15 +30,15 @@ #include <memory> #include <utility> +#include "absl/base/internal/bits.h" #include "absl/base/internal/raw_logging.h" #include "absl/strings/ascii.h" #include "absl/strings/charconv.h" -#include "absl/strings/internal/bits.h" #include "absl/strings/internal/memutil.h" #include "absl/strings/str_cat.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { bool SimpleAtof(absl::string_view str, float* value) { *value = 0.0; @@ -162,8 +162,8 @@ bool SimpleAtob(absl::string_view str, bool* value) { // their output to the beginning of the buffer. The caller is responsible // for ensuring that the buffer has enough space to hold the output. // -// Returns a pointer to the end of the std::string (i.e. the null character -// terminating the std::string). +// Returns a pointer to the end of the string (i.e. the null character +// terminating the string). // ---------------------------------------------------------------------- namespace { @@ -345,7 +345,7 @@ static std::pair<uint64_t, uint64_t> Mul32(std::pair<uint64_t, uint64_t> num, uint64_t bits128_up = (bits96_127 >> 32) + (bits64_127 < bits64_95); if (bits128_up == 0) return {bits64_127, bits0_63}; - int shift = 64 - strings_internal::CountLeadingZeros64(bits128_up); + int shift = 64 - base_internal::CountLeadingZeros64(bits128_up); uint64_t lo = (bits0_63 >> shift) + (bits64_127 << (64 - shift)); uint64_t hi = (bits64_127 >> shift) + (bits128_up << (64 - shift)); return {hi, lo}; @@ -376,7 +376,7 @@ static std::pair<uint64_t, uint64_t> PowFive(uint64_t num, int expfive) { 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5}; result = Mul32(result, powers_of_five[expfive & 15]); - int shift = strings_internal::CountLeadingZeros64(result.first); + int shift = base_internal::CountLeadingZeros64(result.first); if (shift != 0) { result.first = (result.first << shift) + (result.second >> (64 - shift)); result.second = (result.second << shift); @@ -910,5 +910,5 @@ bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base) { } } // namespace numbers_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/numbers.h b/absl/strings/numbers.h index 8efdb200..250d2603 100644 --- a/absl/strings/numbers.h +++ b/absl/strings/numbers.h @@ -38,48 +38,48 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // SimpleAtoi() // -// Converts the given std::string into an integer value, returning `true` if -// successful. The std::string must reflect a base-10 integer (optionally followed or +// Converts the given string into an integer value, returning `true` if +// successful. The string must reflect a base-10 integer (optionally followed or // preceded by ASCII whitespace) whose value falls within the range of the -// integer type, +// integer type. template <typename int_type> ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view s, int_type* out); // SimpleAtof() // -// Converts the given std::string (optionally followed or preceded by ASCII +// Converts the given string (optionally followed or preceded by ASCII // whitespace) into a float, which may be rounded on overflow or underflow. -// See http://en.cppreference.com/w/c/std::string/byte/strtof for details about the +// See http://en.cppreference.com/w/c/string/byte/strtof for details about the // allowed formats for `str`. ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str, float* value); // SimpleAtod() // -// Converts the given std::string (optionally followed or preceded by ASCII +// Converts the given string (optionally followed or preceded by ASCII // whitespace) into a double, which may be rounded on overflow or underflow. -// See http://en.cppreference.com/w/c/std::string/byte/strtof for details about the +// See http://en.cppreference.com/w/c/string/byte/strtof for details about the // allowed formats for `str`. ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str, double* value); // SimpleAtob() // -// Converts the given std::string into a boolean, returning `true` if successful. +// Converts the given string into a boolean, returning `true` if successful. // The following case-insensitive strings are interpreted as boolean `true`: // "true", "t", "yes", "y", "1". The following case-insensitive strings // are interpreted as boolean `false`: "false", "f", "no", "n", "0". ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str, bool* value); -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl // End of public API. Implementation details follow. namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace numbers_internal { // safe_strto?() functions for implementing SimpleAtoi() @@ -172,9 +172,9 @@ ABSL_MUST_USE_RESULT bool safe_strtoi_base(absl::string_view s, int_type* out, // SimpleAtoi() // -// Converts a std::string to an integer, using `safe_strto?()` functions for actual +// Converts a string to an integer, using `safe_strto?()` functions for actual // parsing, returning `true` if successful. The `safe_strto?()` functions apply -// strict checking; the std::string must be a base-10 integer, optionally followed or +// strict checking; the string must be a base-10 integer, optionally followed or // preceded by ASCII whitespace, with a value in the range of the corresponding // integer type. template <typename int_type> @@ -182,7 +182,7 @@ ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view s, int_type* out) { return numbers_internal::safe_strtoi_base(s, out, 10); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_NUMBERS_H_ diff --git a/absl/strings/numbers_benchmark.cc b/absl/strings/numbers_benchmark.cc new file mode 100644 index 00000000..0570b758 --- /dev/null +++ b/absl/strings/numbers_benchmark.cc @@ -0,0 +1,263 @@ +// Copyright 2018 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cstdint> +#include <random> +#include <string> +#include <type_traits> +#include <vector> + +#include "benchmark/benchmark.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/numbers.h" + +namespace { + +template <typename T> +void BM_FastIntToBuffer(benchmark::State& state) { + const int inc = state.range(0); + char buf[absl::numbers_internal::kFastToBufferSize]; + // Use the unsigned type to increment to take advantage of well-defined + // modular arithmetic. + typename std::make_unsigned<T>::type x = 0; + for (auto _ : state) { + absl::numbers_internal::FastIntToBuffer(static_cast<T>(x), buf); + x += inc; + } +} +BENCHMARK_TEMPLATE(BM_FastIntToBuffer, int32_t)->Range(0, 1 << 15); +BENCHMARK_TEMPLATE(BM_FastIntToBuffer, int64_t)->Range(0, 1 << 30); + +// Creates an integer that would be printed as `num_digits` repeated 7s in the +// given `base`. `base` must be greater than or equal to 8. +int64_t RepeatedSevens(int num_digits, int base) { + ABSL_RAW_CHECK(base >= 8, ""); + int64_t num = 7; + while (--num_digits) num = base * num + 7; + return num; +} + +void BM_safe_strto32_string(benchmark::State& state) { + const int digits = state.range(0); + const int base = state.range(1); + std::string str(digits, '7'); // valid in octal, decimal and hex + int32_t value = 0; + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::numbers_internal::safe_strto32_base(str, &value, base)); + } + ABSL_RAW_CHECK(value == RepeatedSevens(digits, base), ""); +} +BENCHMARK(BM_safe_strto32_string) + ->ArgPair(1, 8) + ->ArgPair(1, 10) + ->ArgPair(1, 16) + ->ArgPair(2, 8) + ->ArgPair(2, 10) + ->ArgPair(2, 16) + ->ArgPair(4, 8) + ->ArgPair(4, 10) + ->ArgPair(4, 16) + ->ArgPair(8, 8) + ->ArgPair(8, 10) + ->ArgPair(8, 16) + ->ArgPair(10, 8) + ->ArgPair(9, 10); + +void BM_safe_strto64_string(benchmark::State& state) { + const int digits = state.range(0); + const int base = state.range(1); + std::string str(digits, '7'); // valid in octal, decimal and hex + int64_t value = 0; + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::numbers_internal::safe_strto64_base(str, &value, base)); + } + ABSL_RAW_CHECK(value == RepeatedSevens(digits, base), ""); +} +BENCHMARK(BM_safe_strto64_string) + ->ArgPair(1, 8) + ->ArgPair(1, 10) + ->ArgPair(1, 16) + ->ArgPair(2, 8) + ->ArgPair(2, 10) + ->ArgPair(2, 16) + ->ArgPair(4, 8) + ->ArgPair(4, 10) + ->ArgPair(4, 16) + ->ArgPair(8, 8) + ->ArgPair(8, 10) + ->ArgPair(8, 16) + ->ArgPair(16, 8) + ->ArgPair(16, 10) + ->ArgPair(16, 16); + +void BM_safe_strtou32_string(benchmark::State& state) { + const int digits = state.range(0); + const int base = state.range(1); + std::string str(digits, '7'); // valid in octal, decimal and hex + uint32_t value = 0; + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::numbers_internal::safe_strtou32_base(str, &value, base)); + } + ABSL_RAW_CHECK(value == RepeatedSevens(digits, base), ""); +} +BENCHMARK(BM_safe_strtou32_string) + ->ArgPair(1, 8) + ->ArgPair(1, 10) + ->ArgPair(1, 16) + ->ArgPair(2, 8) + ->ArgPair(2, 10) + ->ArgPair(2, 16) + ->ArgPair(4, 8) + ->ArgPair(4, 10) + ->ArgPair(4, 16) + ->ArgPair(8, 8) + ->ArgPair(8, 10) + ->ArgPair(8, 16) + ->ArgPair(10, 8) + ->ArgPair(9, 10); + +void BM_safe_strtou64_string(benchmark::State& state) { + const int digits = state.range(0); + const int base = state.range(1); + std::string str(digits, '7'); // valid in octal, decimal and hex + uint64_t value = 0; + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::numbers_internal::safe_strtou64_base(str, &value, base)); + } + ABSL_RAW_CHECK(value == RepeatedSevens(digits, base), ""); +} +BENCHMARK(BM_safe_strtou64_string) + ->ArgPair(1, 8) + ->ArgPair(1, 10) + ->ArgPair(1, 16) + ->ArgPair(2, 8) + ->ArgPair(2, 10) + ->ArgPair(2, 16) + ->ArgPair(4, 8) + ->ArgPair(4, 10) + ->ArgPair(4, 16) + ->ArgPair(8, 8) + ->ArgPair(8, 10) + ->ArgPair(8, 16) + ->ArgPair(16, 8) + ->ArgPair(16, 10) + ->ArgPair(16, 16); + +// Returns a vector of `num_strings` strings. Each string represents a +// floating point number with `num_digits` digits before the decimal point and +// another `num_digits` digits after. +std::vector<std::string> MakeFloatStrings(int num_strings, int num_digits) { + // For convenience, use a random number generator to generate the test data. + // We don't actually need random properties, so use a fixed seed. + std::minstd_rand0 rng(1); + std::uniform_int_distribution<int> random_digit('0', '9'); + + std::vector<std::string> float_strings(num_strings); + for (std::string& s : float_strings) { + s.reserve(2 * num_digits + 1); + for (int i = 0; i < num_digits; ++i) { + s.push_back(static_cast<char>(random_digit(rng))); + } + s.push_back('.'); + for (int i = 0; i < num_digits; ++i) { + s.push_back(static_cast<char>(random_digit(rng))); + } + } + return float_strings; +} + +template <typename StringType> +StringType GetStringAs(const std::string& s) { + return static_cast<StringType>(s); +} +template <> +const char* GetStringAs<const char*>(const std::string& s) { + return s.c_str(); +} + +template <typename StringType> +std::vector<StringType> GetStringsAs(const std::vector<std::string>& strings) { + std::vector<StringType> result; + result.reserve(strings.size()); + for (const std::string& s : strings) { + result.push_back(GetStringAs<StringType>(s)); + } + return result; +} + +template <typename T> +void BM_SimpleAtof(benchmark::State& state) { + const int num_strings = state.range(0); + const int num_digits = state.range(1); + std::vector<std::string> backing_strings = + MakeFloatStrings(num_strings, num_digits); + std::vector<T> inputs = GetStringsAs<T>(backing_strings); + float value; + for (auto _ : state) { + for (const T& input : inputs) { + benchmark::DoNotOptimize(absl::SimpleAtof(input, &value)); + } + } +} +BENCHMARK_TEMPLATE(BM_SimpleAtof, absl::string_view) + ->ArgPair(10, 1) + ->ArgPair(10, 2) + ->ArgPair(10, 4) + ->ArgPair(10, 8); +BENCHMARK_TEMPLATE(BM_SimpleAtof, const char*) + ->ArgPair(10, 1) + ->ArgPair(10, 2) + ->ArgPair(10, 4) + ->ArgPair(10, 8); +BENCHMARK_TEMPLATE(BM_SimpleAtof, std::string) + ->ArgPair(10, 1) + ->ArgPair(10, 2) + ->ArgPair(10, 4) + ->ArgPair(10, 8); + +template <typename T> +void BM_SimpleAtod(benchmark::State& state) { + const int num_strings = state.range(0); + const int num_digits = state.range(1); + std::vector<std::string> backing_strings = + MakeFloatStrings(num_strings, num_digits); + std::vector<T> inputs = GetStringsAs<T>(backing_strings); + double value; + for (auto _ : state) { + for (const T& input : inputs) { + benchmark::DoNotOptimize(absl::SimpleAtod(input, &value)); + } + } +} +BENCHMARK_TEMPLATE(BM_SimpleAtod, absl::string_view) + ->ArgPair(10, 1) + ->ArgPair(10, 2) + ->ArgPair(10, 4) + ->ArgPair(10, 8); +BENCHMARK_TEMPLATE(BM_SimpleAtod, const char*) + ->ArgPair(10, 1) + ->ArgPair(10, 2) + ->ArgPair(10, 4) + ->ArgPair(10, 8); +BENCHMARK_TEMPLATE(BM_SimpleAtod, std::string) + ->ArgPair(10, 1) + ->ArgPair(10, 2) + ->ArgPair(10, 4) + ->ArgPair(10, 8); + +} // namespace diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc index 24e7138c..099326c2 100644 --- a/absl/strings/numbers_test.cc +++ b/absl/strings/numbers_test.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file tests std::string processing functions related to numeric values. +// This file tests string processing functions related to numeric values. #include "absl/strings/numbers.h" @@ -39,6 +39,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/internal/numbers_test_common.h" +#include "absl/strings/internal/pow10_helper.h" namespace { @@ -56,16 +57,11 @@ using testing::Eq; using testing::MatchesRegex; // Number of floats to test with. -// 10,000,000 is a reasonable default for a test that only takes a few seconds. +// 5,000,000 is a reasonable default for a test that only takes a few seconds. // 1,000,000,000+ triggers checking for all possible mantissa values for // double-precision tests. 2,000,000,000+ triggers checking for every possible // single-precision float. -#ifdef _MSC_VER -// Use a smaller number on MSVC to avoid test time out (1 min) const int kFloatNumCases = 5000000; -#else -const int kFloatNumCases = 10000000; -#endif // This is a slow, brute-force routine to compute the exact base-10 // representation of a double-precision floating-point number. It @@ -716,8 +712,9 @@ TEST(stringtest, safe_strtou64_base_length_delimited) { } } -// feenableexcept() and fedisableexcept() are missing on Mac OS X, MSVC. -#if defined(_MSC_VER) || defined(__APPLE__) +// feenableexcept() and fedisableexcept() are missing on Mac OS X, MSVC, +// and WebAssembly. +#if defined(_MSC_VER) || defined(__APPLE__) || defined(__EMSCRIPTEN__) #define ABSL_MISSING_FEENABLEEXCEPT 1 #define ABSL_MISSING_FEDISABLEEXCEPT 1 #endif @@ -875,7 +872,7 @@ TEST_F(SimpleDtoaTest, ExhaustiveDoubleToSixDigits) { } for (int exponent = -324; exponent <= 308; ++exponent) { - double powten = pow(10.0, exponent); + double powten = absl::strings_internal::Pow10(exponent); if (powten == 0) powten = 5e-324; if (kFloatNumCases >= 1e9) { // The exhaustive test takes a very long time, so log progress. diff --git a/absl/strings/str_cat.cc b/absl/strings/str_cat.cc index 499f3f41..2f2e5315 100644 --- a/absl/strings/str_cat.cc +++ b/absl/strings/str_cat.cc @@ -23,7 +23,7 @@ #include "absl/strings/internal/resize_uninitialized.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { AlphaNum::AlphaNum(Hex hex) { char* const end = &digits_[numbers_internal::kFastToBufferSize]; @@ -80,8 +80,8 @@ AlphaNum::AlphaNum(Dec dec) { // ---------------------------------------------------------------------- // StrCat() // This merges the given strings or integers, with no delimiter. This -// is designed to be the fastest possible way to construct a std::string out -// of a mix of raw C strings, StringPieces, strings, and integer values. +// is designed to be the fastest possible way to construct a string out +// of a mix of raw C strings, string_views, strings, and integer values. // ---------------------------------------------------------------------- // Append is merely a version of memcpy that returns the address of the byte @@ -155,10 +155,10 @@ std::string CatPieces(std::initializer_list<absl::string_view> pieces) { } // It's possible to call StrAppend with an absl::string_view that is itself a -// fragment of the std::string we're appending to. However the results of this are +// fragment of the string we're appending to. However the results of this are // random. Therefore, check for this in debug mode. Use unsigned math so we // only have to do one comparison. Note, there's an exception case: appending an -// empty std::string is always allowed. +// empty string is always allowed. #define ASSERT_NO_OVERLAP(dest, src) \ assert(((src).size() == 0) || \ (uintptr_t((src).data() - (dest).data()) > uintptr_t((dest).size()))) @@ -237,5 +237,5 @@ void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, assert(out == begin + dest->size()); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index c694cbc1..edda40ad 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h @@ -23,7 +23,7 @@ // designed to be used as a parameter type that efficiently manages conversion // to strings and avoids copies in the above operations. // -// Any routine accepting either a std::string or a number may accept `AlphaNum`. +// Any routine accepting either a string or a number may accept `AlphaNum`. // The basic idea is that by accepting a `const AlphaNum &` as an argument // to your function, your callers will automagically convert bools, integers, // and floating point values to strings for you. @@ -63,10 +63,10 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { -// AlphaNumBuffer allows a way to pass a std::string to StrCat without having to do +// AlphaNumBuffer allows a way to pass a string to StrCat without having to do // memory allocation. It is simply a pair of a fixed-size character array, and // a size. Please don't use outside of absl, yet. template <size_t max_size> @@ -98,6 +98,10 @@ enum PadSpec : uint8_t { kZeroPad14, kZeroPad15, kZeroPad16, + kZeroPad17, + kZeroPad18, + kZeroPad19, + kZeroPad20, kSpacePad2 = kZeroPad2 + 64, kSpacePad3, @@ -114,14 +118,18 @@ enum PadSpec : uint8_t { kSpacePad14, kSpacePad15, kSpacePad16, + kSpacePad17, + kSpacePad18, + kSpacePad19, + kSpacePad20, }; // ----------------------------------------------------------------------------- // Hex // ----------------------------------------------------------------------------- // -// `Hex` stores a set of hexadecimal std::string conversion parameters for use -// within `AlphaNum` std::string conversions. +// `Hex` stores a set of hexadecimal string conversion parameters for use +// within `AlphaNum` string conversions. struct Hex { uint64_t value; uint8_t width; @@ -169,8 +177,8 @@ struct Hex { // Dec // ----------------------------------------------------------------------------- // -// `Dec` stores a set of decimal std::string conversion parameters for use -// within `AlphaNum` std::string conversions. Dec is slower than the default +// `Dec` stores a set of decimal string conversion parameters for use +// within `AlphaNum` string conversions. Dec is slower than the default // integer conversion, so use it only if you need padding. struct Dec { uint64_t value; @@ -272,7 +280,7 @@ class AlphaNum { // // Merges given strings or numbers, using no delimiter(s). // -// `StrCat()` is designed to be the fastest possible way to construct a std::string +// `StrCat()` is designed to be the fastest possible way to construct a string // out of a mix of raw C strings, string_views, strings, bool values, // and numeric values. // @@ -280,7 +288,7 @@ class AlphaNum { // works poorly on strings built up out of fragments. // // For clarity and performance, don't use `StrCat()` when appending to a -// std::string. Use `StrAppend()` instead. In particular, avoid using any of these +// string. Use `StrAppend()` instead. In particular, avoid using any of these // (anti-)patterns: // // str.append(StrCat(...)) @@ -329,26 +337,26 @@ ABSL_MUST_USE_RESULT inline std::string StrCat(const AlphaNum& a, const AlphaNum // StrAppend() // ----------------------------------------------------------------------------- // -// Appends a std::string or set of strings to an existing std::string, in a similar +// Appends a string or set of strings to an existing string, in a similar // fashion to `StrCat()`. // // WARNING: `StrAppend(&str, a, b, c, ...)` requires that none of the // a, b, c, parameters be a reference into str. For speed, `StrAppend()` does // not try to check each of its input arguments to be sure that they are not -// a subset of the std::string being appended to. That is, while this will work: +// a subset of the string being appended to. That is, while this will work: // -// std::string s = "foo"; +// string s = "foo"; // s += s; // // This output is undefined: // -// std::string s = "foo"; +// string s = "foo"; // StrAppend(&s, s); // // This output is undefined as well, since `absl::string_view` does not own its // data: // -// std::string s = "foobar"; +// string s = "foobar"; // absl::string_view p = s; // StrAppend(&s, p); @@ -381,7 +389,7 @@ SixDigits(double d) { return result; } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_STR_CAT_H_ diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc index e9d67690..07141072 100644 --- a/absl/strings/str_cat_test.cc +++ b/absl/strings/str_cat_test.cc @@ -28,7 +28,8 @@ #define ABSL_EXPECT_DEBUG_DEATH(statement, regex) \ EXPECT_DEBUG_DEATH(statement, ".*") #else -#define ABSL_EXPECT_DEBUG_DEATH EXPECT_DEBUG_DEATH +#define ABSL_EXPECT_DEBUG_DEATH(statement, regex) \ + EXPECT_DEBUG_DEATH(statement, regex) #endif namespace { @@ -426,7 +427,7 @@ void CheckHex(IntType v, const char* nopad_format, const char* zeropad_format, snprintf(expected, sizeof(expected), nopad_format, v); EXPECT_EQ(expected, actual) << " decimal value " << v; - for (int spec = absl::kZeroPad2; spec <= absl::kZeroPad16; ++spec) { + for (int spec = absl::kZeroPad2; spec <= absl::kZeroPad20; ++spec) { std::string actual = absl::StrCat(absl::Hex(v, static_cast<absl::PadSpec>(spec))); snprintf(expected, sizeof(expected), zeropad_format, @@ -434,7 +435,7 @@ void CheckHex(IntType v, const char* nopad_format, const char* zeropad_format, EXPECT_EQ(expected, actual) << " decimal value " << v; } - for (int spec = absl::kSpacePad2; spec <= absl::kSpacePad16; ++spec) { + for (int spec = absl::kSpacePad2; spec <= absl::kSpacePad20; ++spec) { std::string actual = absl::StrCat(absl::Hex(v, static_cast<absl::PadSpec>(spec))); snprintf(expected, sizeof(expected), spacepad_format, @@ -452,7 +453,7 @@ void CheckDec(IntType v, const char* nopad_format, const char* zeropad_format, snprintf(expected, sizeof(expected), nopad_format, v); EXPECT_EQ(expected, actual) << " decimal value " << v; - for (int spec = absl::kZeroPad2; spec <= absl::kZeroPad16; ++spec) { + for (int spec = absl::kZeroPad2; spec <= absl::kZeroPad20; ++spec) { std::string actual = absl::StrCat(absl::Dec(v, static_cast<absl::PadSpec>(spec))); snprintf(expected, sizeof(expected), zeropad_format, @@ -462,7 +463,7 @@ void CheckDec(IntType v, const char* nopad_format, const char* zeropad_format, << "' digits " << (spec - absl::kZeroPad2 + 2); } - for (int spec = absl::kSpacePad2; spec <= absl::kSpacePad16; ++spec) { + for (int spec = absl::kSpacePad2; spec <= absl::kSpacePad20; ++spec) { std::string actual = absl::StrCat(absl::Dec(v, static_cast<absl::PadSpec>(spec))); snprintf(expected, sizeof(expected), spacepad_format, diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h new file mode 100644 index 00000000..7b19d411 --- /dev/null +++ b/absl/strings/str_format.h @@ -0,0 +1,514 @@ +// +// Copyright 2018 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: str_format.h +// ----------------------------------------------------------------------------- +// +// The `str_format` library is a typesafe replacement for the family of +// `printf()` string formatting routines within the `<cstdio>` standard library +// header. Like the `printf` family, the `str_format` uses a "format string" to +// perform argument substitutions based on types. +// +// Example: +// +// string s = absl::StrFormat("%s %s You have $%d!", "Hello", name, dollars); +// +// The library consists of the following basic utilities: +// +// * `absl::StrFormat()`, a type-safe replacement for `std::sprintf()`, to +// write a format string to a `string` value. +// * `absl::StrAppendFormat()` to append a format string to a `string` +// * `absl::StreamFormat()` to more efficiently write a format string to a +// stream, such as`std::cout`. +// * `absl::PrintF()`, `absl::FPrintF()` and `absl::SNPrintF()` as +// replacements for `std::printf()`, `std::fprintf()` and `std::snprintf()`. +// +// Note: a version of `std::sprintf()` is not supported as it is +// generally unsafe due to buffer overflows. +// +// Additionally, you can provide a format string (and its associated arguments) +// using one of the following abstractions: +// +// * A `FormatSpec` class template fully encapsulates a format string and its +// type arguments and is usually provided to `str_format` functions as a +// variadic argument of type `FormatSpec<Arg...>`. The `FormatSpec<Args...>` +// template is evaluated at compile-time, providing type safety. +// * A `ParsedFormat` instance, which encapsulates a specific, pre-compiled +// format string for a specific set of type(s), and which can be passed +// between API boundaries. (The `FormatSpec` type should not be used +// directly.) +// +// The `str_format` library provides the ability to output its format strings to +// arbitrary sink types: +// +// * A generic `Format()` function to write outputs to arbitrary sink types, +// which must implement a `RawSinkFormat` interface. (See +// `str_format_sink.h` for more information.) +// +// * A `FormatUntyped()` function that is similar to `Format()` except it is +// loosely typed. `FormatUntyped()` is not a template and does not perform +// any compile-time checking of the format string; instead, it returns a +// boolean from a runtime check. +// +// In addition, the `str_format` library provides extension points for +// augmenting formatting to new types. These extensions are fully documented +// within the `str_format_extension.h` header file. +#ifndef ABSL_STRINGS_STR_FORMAT_H_ +#define ABSL_STRINGS_STR_FORMAT_H_ + +#include <cstdio> +#include <string> + +#include "absl/strings/internal/str_format/arg.h" // IWYU pragma: export +#include "absl/strings/internal/str_format/bind.h" // IWYU pragma: export +#include "absl/strings/internal/str_format/checker.h" // IWYU pragma: export +#include "absl/strings/internal/str_format/extension.h" // IWYU pragma: export +#include "absl/strings/internal/str_format/parser.h" // IWYU pragma: export + +namespace absl { +inline namespace lts_2018_12_18 { + +// UntypedFormatSpec +// +// A type-erased class that can be used directly within untyped API entry +// points. An `UntypedFormatSpec` is specifically used as an argument to +// `FormatUntyped()`. +// +// Example: +// +// absl::UntypedFormatSpec format("%d"); +// string out; +// CHECK(absl::FormatUntyped(&out, format, {absl::FormatArg(1)})); +class UntypedFormatSpec { + public: + UntypedFormatSpec() = delete; + UntypedFormatSpec(const UntypedFormatSpec&) = delete; + UntypedFormatSpec& operator=(const UntypedFormatSpec&) = delete; + + explicit UntypedFormatSpec(string_view s) : spec_(s) {} + + protected: + explicit UntypedFormatSpec(const str_format_internal::ParsedFormatBase* pc) + : spec_(pc) {} + + private: + friend str_format_internal::UntypedFormatSpecImpl; + str_format_internal::UntypedFormatSpecImpl spec_; +}; + +// FormatStreamed() +// +// Takes a streamable argument and returns an object that can print it +// with '%s'. Allows printing of types that have an `operator<<` but no +// intrinsic type support within `StrFormat()` itself. +// +// Example: +// +// absl::StrFormat("%s", absl::FormatStreamed(obj)); +template <typename T> +str_format_internal::StreamedWrapper<T> FormatStreamed(const T& v) { + return str_format_internal::StreamedWrapper<T>(v); +} + +// FormatCountCapture +// +// This class provides a way to safely wrap `StrFormat()` captures of `%n` +// conversions, which denote the number of characters written by a formatting +// operation to this point, into an integer value. +// +// This wrapper is designed to allow safe usage of `%n` within `StrFormat(); in +// the `printf()` family of functions, `%n` is not safe to use, as the `int *` +// buffer can be used to capture arbitrary data. +// +// Example: +// +// int n = 0; +// string s = absl::StrFormat("%s%d%n", "hello", 123, +// absl::FormatCountCapture(&n)); +// EXPECT_EQ(8, n); +class FormatCountCapture { + public: + explicit FormatCountCapture(int* p) : p_(p) {} + + private: + // FormatCountCaptureHelper is used to define FormatConvertImpl() for this + // class. + friend struct str_format_internal::FormatCountCaptureHelper; + // Unused() is here because of the false positive from -Wunused-private-field + // p_ is used in the templated function of the friend FormatCountCaptureHelper + // class. + int* Unused() { return p_; } + int* p_; +}; + +// FormatSpec +// +// The `FormatSpec` type defines the makeup of a format string within the +// `str_format` library. You should not need to use or manipulate this type +// directly. A `FormatSpec` is a variadic class template that is evaluated at +// compile-time, according to the format string and arguments that are passed +// to it. +// +// For a `FormatSpec` to be valid at compile-time, it must be provided as +// either: +// +// * A `constexpr` literal or `absl::string_view`, which is how it most often +// used. +// * A `ParsedFormat` instantiation, which ensures the format string is +// valid before use. (See below.) +// +// Example: +// +// // Provided as a string literal. +// absl::StrFormat("Welcome to %s, Number %d!", "The Village", 6); +// +// // Provided as a constexpr absl::string_view. +// constexpr absl::string_view formatString = "Welcome to %s, Number %d!"; +// absl::StrFormat(formatString, "The Village", 6); +// +// // Provided as a pre-compiled ParsedFormat object. +// // Note that this example is useful only for illustration purposes. +// absl::ParsedFormat<'s', 'd'> formatString("Welcome to %s, Number %d!"); +// absl::StrFormat(formatString, "TheVillage", 6); +// +// A format string generally follows the POSIX syntax as used within the POSIX +// `printf` specification. +// +// (See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html.) +// +// In specific, the `FormatSpec` supports the following type specifiers: +// * `c` for characters +// * `s` for strings +// * `d` or `i` for integers +// * `o` for unsigned integer conversions into octal +// * `x` or `X` for unsigned integer conversions into hex +// * `u` for unsigned integers +// * `f` or `F` for floating point values into decimal notation +// * `e` or `E` for floating point values into exponential notation +// * `a` or `A` for floating point values into hex exponential notation +// * `g` or `G` for floating point values into decimal or exponential +// notation based on their precision +// * `p` for pointer address values +// * `n` for the special case of writing out the number of characters +// written to this point. The resulting value must be captured within an +// `absl::FormatCountCapture` type. +// +// NOTE: `o`, `x\X` and `u` will convert signed values to their unsigned +// counterpart before formatting. +// +// Examples: +// "%c", 'a' -> "a" +// "%c", 32 -> " " +// "%s", "C" -> "C" +// "%s", std::string("C++") -> "C++" +// "%d", -10 -> "-10" +// "%o", 10 -> "12" +// "%x", 16 -> "10" +// "%f", 123456789 -> "123456789.000000" +// "%e", .01 -> "1.00000e-2" +// "%a", -3.0 -> "-0x1.8p+1" +// "%g", .01 -> "1e-2" +// "%p", *int -> "0x7ffdeb6ad2a4" +// +// int n = 0; +// string s = absl::StrFormat( +// "%s%d%n", "hello", 123, absl::FormatCountCapture(&n)); +// EXPECT_EQ(8, n); +// +// The `FormatSpec` intrinsically supports all of these fundamental C++ types: +// +// * Characters: `char`, `signed char`, `unsigned char` +// * Integers: `int`, `short`, `unsigned short`, `unsigned`, `long`, +// `unsigned long`, `long long`, `unsigned long long` +// * Floating-point: `float`, `double`, `long double` +// +// However, in the `str_format` library, a format conversion specifies a broader +// C++ conceptual category instead of an exact type. For example, `%s` binds to +// any string-like argument, so `std::string`, `absl::string_view`, and +// `const char*` are all accepted. Likewise, `%d` accepts any integer-like +// argument, etc. + +template <typename... Args> +using FormatSpec = + typename str_format_internal::FormatSpecDeductionBarrier<Args...>::type; + +// ParsedFormat +// +// A `ParsedFormat` is a class template representing a preparsed `FormatSpec`, +// with template arguments specifying the conversion characters used within the +// format string. Such characters must be valid format type specifiers, and +// these type specifiers are checked at compile-time. +// +// Instances of `ParsedFormat` can be created, copied, and reused to speed up +// formatting loops. A `ParsedFormat` may either be constructed statically, or +// dynamically through its `New()` factory function, which only constructs a +// runtime object if the format is valid at that time. +// +// Example: +// +// // Verified at compile time. +// absl::ParsedFormat<'s', 'd'> formatString("Welcome to %s, Number %d!"); +// absl::StrFormat(formatString, "TheVillage", 6); +// +// // Verified at runtime. +// auto format_runtime = absl::ParsedFormat<'d'>::New(format_string); +// if (format_runtime) { +// value = absl::StrFormat(*format_runtime, i); +// } else { +// ... error case ... +// } +template <char... Conv> +using ParsedFormat = str_format_internal::ExtendedParsedFormat< + str_format_internal::ConversionCharToConv(Conv)...>; + +// StrFormat() +// +// Returns a `string` given a `printf()`-style format string and zero or more +// additional arguments. Use it as you would `sprintf()`. `StrFormat()` is the +// primary formatting function within the `str_format` library, and should be +// used in most cases where you need type-safe conversion of types into +// formatted strings. +// +// The format string generally consists of ordinary character data along with +// one or more format conversion specifiers (denoted by the `%` character). +// Ordinary character data is returned unchanged into the result string, while +// each conversion specification performs a type substitution from +// `StrFormat()`'s other arguments. See the comments for `FormatSpec` for full +// information on the makeup of this format string. +// +// Example: +// +// string s = absl::StrFormat( +// "Welcome to %s, Number %d!", "The Village", 6); +// EXPECT_EQ("Welcome to The Village, Number 6!", s); +// +// Returns an empty string in case of error. +template <typename... Args> +ABSL_MUST_USE_RESULT std::string StrFormat(const FormatSpec<Args...>& format, + const Args&... args) { + return str_format_internal::FormatPack( + str_format_internal::UntypedFormatSpecImpl::Extract(format), + {str_format_internal::FormatArgImpl(args)...}); +} + +// StrAppendFormat() +// +// Appends to a `dst` string given a format string, and zero or more additional +// arguments, returning `*dst` as a convenience for chaining purposes. Appends +// nothing in case of error (but possibly alters its capacity). +// +// Example: +// +// string orig("For example PI is approximately "); +// std::cout << StrAppendFormat(&orig, "%12.6f", 3.14); +template <typename... Args> +std::string& StrAppendFormat(std::string* dst, const FormatSpec<Args...>& format, + const Args&... args) { + return str_format_internal::AppendPack( + dst, str_format_internal::UntypedFormatSpecImpl::Extract(format), + {str_format_internal::FormatArgImpl(args)...}); +} + +// StreamFormat() +// +// Writes to an output stream given a format string and zero or more arguments, +// generally in a manner that is more efficient than streaming the result of +// `absl:: StrFormat()`. The returned object must be streamed before the full +// expression ends. +// +// Example: +// +// std::cout << StreamFormat("%12.6f", 3.14); +template <typename... Args> +ABSL_MUST_USE_RESULT str_format_internal::Streamable StreamFormat( + const FormatSpec<Args...>& format, const Args&... args) { + return str_format_internal::Streamable( + str_format_internal::UntypedFormatSpecImpl::Extract(format), + {str_format_internal::FormatArgImpl(args)...}); +} + +// PrintF() +// +// Writes to stdout given a format string and zero or more arguments. This +// function is functionally equivalent to `std::printf()` (and type-safe); +// prefer `absl::PrintF()` over `std::printf()`. +// +// Example: +// +// std::string_view s = "Ulaanbaatar"; +// absl::PrintF("The capital of Mongolia is %s", s); +// +// Outputs: "The capital of Mongolia is Ulaanbaatar" +// +template <typename... Args> +int PrintF(const FormatSpec<Args...>& format, const Args&... args) { + return str_format_internal::FprintF( + stdout, str_format_internal::UntypedFormatSpecImpl::Extract(format), + {str_format_internal::FormatArgImpl(args)...}); +} + +// FPrintF() +// +// Writes to a file given a format string and zero or more arguments. This +// function is functionally equivalent to `std::fprintf()` (and type-safe); +// prefer `absl::FPrintF()` over `std::fprintf()`. +// +// Example: +// +// std::string_view s = "Ulaanbaatar"; +// absl::FPrintF(stdout, "The capital of Mongolia is %s", s); +// +// Outputs: "The capital of Mongolia is Ulaanbaatar" +// +template <typename... Args> +int FPrintF(std::FILE* output, const FormatSpec<Args...>& format, + const Args&... args) { + return str_format_internal::FprintF( + output, str_format_internal::UntypedFormatSpecImpl::Extract(format), + {str_format_internal::FormatArgImpl(args)...}); +} + +// SNPrintF() +// +// Writes to a sized buffer given a format string and zero or more arguments. +// This function is functionally equivalent to `std::snprintf()` (and +// type-safe); prefer `absl::SNPrintF()` over `std::snprintf()`. +// +// Example: +// +// std::string_view s = "Ulaanbaatar"; +// char output[128]; +// absl::SNPrintF(output, sizeof(output), +// "The capital of Mongolia is %s", s); +// +// Post-condition: output == "The capital of Mongolia is Ulaanbaatar" +// +template <typename... Args> +int SNPrintF(char* output, std::size_t size, const FormatSpec<Args...>& format, + const Args&... args) { + return str_format_internal::SnprintF( + output, size, str_format_internal::UntypedFormatSpecImpl::Extract(format), + {str_format_internal::FormatArgImpl(args)...}); +} + +// ----------------------------------------------------------------------------- +// Custom Output Formatting Functions +// ----------------------------------------------------------------------------- + +// FormatRawSink +// +// FormatRawSink is a type erased wrapper around arbitrary sink objects +// specifically used as an argument to `Format()`. +// FormatRawSink does not own the passed sink object. The passed object must +// outlive the FormatRawSink. +class FormatRawSink { + public: + // Implicitly convert from any type that provides the hook function as + // described above. + template <typename T, + typename = typename std::enable_if<std::is_constructible< + str_format_internal::FormatRawSinkImpl, T*>::value>::type> + FormatRawSink(T* raw) // NOLINT + : sink_(raw) {} + + private: + friend str_format_internal::FormatRawSinkImpl; + str_format_internal::FormatRawSinkImpl sink_; +}; + +// Format() +// +// Writes a formatted string to an arbitrary sink object (implementing the +// `absl::FormatRawSink` interface), using a format string and zero or more +// additional arguments. +// +// By default, `string` and `std::ostream` are supported as destination objects. +// +// `absl::Format()` is a generic version of `absl::StrFormat(), for custom +// sinks. The format string, like format strings for `StrFormat()`, is checked +// at compile-time. +// +// On failure, this function returns `false` and the state of the sink is +// unspecified. +template <typename... Args> +bool Format(FormatRawSink raw_sink, const FormatSpec<Args...>& format, + const Args&... args) { + return str_format_internal::FormatUntyped( + str_format_internal::FormatRawSinkImpl::Extract(raw_sink), + str_format_internal::UntypedFormatSpecImpl::Extract(format), + {str_format_internal::FormatArgImpl(args)...}); +} + +// FormatArg +// +// A type-erased handle to a format argument specifically used as an argument to +// `FormatUntyped()`. You may construct `FormatArg` by passing +// reference-to-const of any printable type. `FormatArg` is both copyable and +// assignable. The source data must outlive the `FormatArg` instance. See +// example below. +// +using FormatArg = str_format_internal::FormatArgImpl; + +// FormatUntyped() +// +// Writes a formatted string to an arbitrary sink object (implementing the +// `absl::FormatRawSink` interface), using an `UntypedFormatSpec` and zero or +// more additional arguments. +// +// This function acts as the most generic formatting function in the +// `str_format` library. The caller provides a raw sink, an unchecked format +// string, and (usually) a runtime specified list of arguments; no compile-time +// checking of formatting is performed within this function. As a result, a +// caller should check the return value to verify that no error occurred. +// On failure, this function returns `false` and the state of the sink is +// unspecified. +// +// The arguments are provided in an `absl::Span<const absl::FormatArg>`. +// Each `absl::FormatArg` object binds to a single argument and keeps a +// reference to it. The values used to create the `FormatArg` objects must +// outlive this function call. (See `str_format_arg.h` for information on +// the `FormatArg` class.)_ +// +// Example: +// +// std::optional<string> FormatDynamic(const string& in_format, +// const vector<string>& in_args) { +// string out; +// std::vector<absl::FormatArg> args; +// for (const auto& v : in_args) { +// // It is important that 'v' is a reference to the objects in in_args. +// // The values we pass to FormatArg must outlive the call to +// // FormatUntyped. +// args.emplace_back(v); +// } +// absl::UntypedFormatSpec format(in_format); +// if (!absl::FormatUntyped(&out, format, args)) { +// return std::nullopt; +// } +// return std::move(out); +// } +// +ABSL_MUST_USE_RESULT inline bool FormatUntyped( + FormatRawSink raw_sink, const UntypedFormatSpec& format, + absl::Span<const FormatArg> args) { + return str_format_internal::FormatUntyped( + str_format_internal::FormatRawSinkImpl::Extract(raw_sink), + str_format_internal::UntypedFormatSpecImpl::Extract(format), args); +} + +} // inline namespace lts_2018_12_18 +} // namespace absl +#endif // ABSL_STRINGS_STR_FORMAT_H_ diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc new file mode 100644 index 00000000..77b8647f --- /dev/null +++ b/absl/strings/str_format_test.cc @@ -0,0 +1,628 @@ + +#include <cstdarg> +#include <cstdint> +#include <cstdio> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" + +namespace absl { +inline namespace lts_2018_12_18 { +namespace { +using str_format_internal::FormatArgImpl; + +class FormatEntryPointTest : public ::testing::Test { }; + +TEST_F(FormatEntryPointTest, Format) { + std::string sink; + EXPECT_TRUE(Format(&sink, "A format %d", 123)); + EXPECT_EQ("A format 123", sink); + sink.clear(); + + ParsedFormat<'d'> pc("A format %d"); + EXPECT_TRUE(Format(&sink, pc, 123)); + EXPECT_EQ("A format 123", sink); +} +TEST_F(FormatEntryPointTest, UntypedFormat) { + constexpr const char* formats[] = { + "", + "a", + "%80d", +#if !defined(_MSC_VER) && !defined(__ANDROID__) + // MSVC and Android don't support positional syntax. + "complicated multipart %% %1$d format %1$0999d", +#endif // _MSC_VER + }; + for (const char* fmt : formats) { + std::string actual; + int i = 123; + FormatArgImpl arg_123(i); + absl::Span<const FormatArgImpl> args(&arg_123, 1); + UntypedFormatSpec format(fmt); + + EXPECT_TRUE(FormatUntyped(&actual, format, args)); + char buf[4096]{}; + snprintf(buf, sizeof(buf), fmt, 123); + EXPECT_EQ( + str_format_internal::FormatPack( + str_format_internal::UntypedFormatSpecImpl::Extract(format), args), + buf); + EXPECT_EQ(actual, buf); + } + // The internal version works with a preparsed format. + ParsedFormat<'d'> pc("A format %d"); + int i = 345; + FormatArg arg(i); + std::string out; + EXPECT_TRUE(str_format_internal::FormatUntyped( + &out, str_format_internal::UntypedFormatSpecImpl(&pc), {&arg, 1})); + EXPECT_EQ("A format 345", out); +} + +TEST_F(FormatEntryPointTest, StringFormat) { + EXPECT_EQ("123", StrFormat("%d", 123)); + constexpr absl::string_view view("=%d=", 4); + EXPECT_EQ("=123=", StrFormat(view, 123)); +} + +TEST_F(FormatEntryPointTest, AppendFormat) { + std::string s; + std::string& r = StrAppendFormat(&s, "%d", 123); + EXPECT_EQ(&s, &r); // should be same object + EXPECT_EQ("123", r); +} + +TEST_F(FormatEntryPointTest, AppendFormatFail) { + std::string s = "orig"; + + UntypedFormatSpec format(" more %d"); + FormatArgImpl arg("not an int"); + + EXPECT_EQ("orig", + str_format_internal::AppendPack( + &s, str_format_internal::UntypedFormatSpecImpl::Extract(format), + {&arg, 1})); +} + + +TEST_F(FormatEntryPointTest, ManyArgs) { + EXPECT_EQ("24", StrFormat("%24$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)); + EXPECT_EQ("60", StrFormat("%60$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60)); +} + +TEST_F(FormatEntryPointTest, Preparsed) { + ParsedFormat<'d'> pc("%d"); + EXPECT_EQ("123", StrFormat(pc, 123)); + // rvalue ok? + EXPECT_EQ("123", StrFormat(ParsedFormat<'d'>("%d"), 123)); + constexpr absl::string_view view("=%d=", 4); + EXPECT_EQ("=123=", StrFormat(ParsedFormat<'d'>(view), 123)); +} + +TEST_F(FormatEntryPointTest, FormatCountCapture) { + int n = 0; + EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n))); + EXPECT_EQ(0, n); + EXPECT_EQ("123", StrFormat("%d%n", 123, FormatCountCapture(&n))); + EXPECT_EQ(3, n); +} + +TEST_F(FormatEntryPointTest, FormatCountCaptureWrongType) { + // Should reject int*. + int n = 0; + UntypedFormatSpec format("%d%n"); + int i = 123, *ip = &n; + FormatArgImpl args[2] = {FormatArgImpl(i), FormatArgImpl(ip)}; + + EXPECT_EQ("", str_format_internal::FormatPack( + str_format_internal::UntypedFormatSpecImpl::Extract(format), + absl::MakeSpan(args))); +} + +TEST_F(FormatEntryPointTest, FormatCountCaptureMultiple) { + int n1 = 0; + int n2 = 0; + EXPECT_EQ(" 1 2", + StrFormat("%5d%n%10d%n", 1, FormatCountCapture(&n1), 2, + FormatCountCapture(&n2))); + EXPECT_EQ(5, n1); + EXPECT_EQ(15, n2); +} + +TEST_F(FormatEntryPointTest, FormatCountCaptureExample) { + int n; + std::string s; + StrAppendFormat(&s, "%s: %n%s\n", "(1,1)", FormatCountCapture(&n), "(1,2)"); + StrAppendFormat(&s, "%*s%s\n", n, "", "(2,2)"); + EXPECT_EQ(7, n); + EXPECT_EQ( + "(1,1): (1,2)\n" + " (2,2)\n", + s); +} + +TEST_F(FormatEntryPointTest, Stream) { + const std::string formats[] = { + "", + "a", + "%80d", +#if !defined(_MSC_VER) && !defined(__ANDROID__) + // MSVC doesn't support positional syntax. + "complicated multipart %% %1$d format %1$080d", +#endif // _MSC_VER + }; + std::string buf(4096, '\0'); + for (const auto& fmt : formats) { + const auto parsed = ParsedFormat<'d'>::NewAllowIgnored(fmt); + std::ostringstream oss; + oss << StreamFormat(*parsed, 123); + int fmt_result = snprintf(&*buf.begin(), buf.size(), fmt.c_str(), 123); + ASSERT_TRUE(oss) << fmt; + ASSERT_TRUE(fmt_result >= 0 && static_cast<size_t>(fmt_result) < buf.size()) + << fmt_result; + EXPECT_EQ(buf.c_str(), oss.str()); + } +} + +TEST_F(FormatEntryPointTest, StreamOk) { + std::ostringstream oss; + oss << StreamFormat("hello %d", 123); + EXPECT_EQ("hello 123", oss.str()); + EXPECT_TRUE(oss.good()); +} + +TEST_F(FormatEntryPointTest, StreamFail) { + std::ostringstream oss; + UntypedFormatSpec format("hello %d"); + FormatArgImpl arg("non-numeric"); + oss << str_format_internal::Streamable( + str_format_internal::UntypedFormatSpecImpl::Extract(format), {&arg, 1}); + EXPECT_EQ("hello ", oss.str()); // partial write + EXPECT_TRUE(oss.fail()); +} + +std::string WithSnprintf(const char* fmt, ...) { + std::string buf; + buf.resize(128); + va_list va; + va_start(va, fmt); + int r = vsnprintf(&*buf.begin(), buf.size(), fmt, va); + va_end(va); + EXPECT_GE(r, 0); + EXPECT_LT(r, buf.size()); + buf.resize(r); + return buf; +} + +TEST_F(FormatEntryPointTest, FloatPrecisionArg) { + // Test that positional parameters for width and precision + // are indexed to precede the value. + // Also sanity check the same formats against snprintf. + EXPECT_EQ("0.1", StrFormat("%.1f", 0.1)); + EXPECT_EQ("0.1", WithSnprintf("%.1f", 0.1)); + EXPECT_EQ(" 0.1", StrFormat("%*.1f", 5, 0.1)); + EXPECT_EQ(" 0.1", WithSnprintf("%*.1f", 5, 0.1)); + EXPECT_EQ("0.1", StrFormat("%.*f", 1, 0.1)); + EXPECT_EQ("0.1", WithSnprintf("%.*f", 1, 0.1)); + EXPECT_EQ(" 0.1", StrFormat("%*.*f", 5, 1, 0.1)); + EXPECT_EQ(" 0.1", WithSnprintf("%*.*f", 5, 1, 0.1)); +} +namespace streamed_test { +struct X {}; +std::ostream& operator<<(std::ostream& os, const X&) { + return os << "X"; +} +} // streamed_test + +TEST_F(FormatEntryPointTest, FormatStreamed) { + EXPECT_EQ("123", StrFormat("%s", FormatStreamed(123))); + EXPECT_EQ(" 123", StrFormat("%5s", FormatStreamed(123))); + EXPECT_EQ("123 ", StrFormat("%-5s", FormatStreamed(123))); + EXPECT_EQ("X", StrFormat("%s", FormatStreamed(streamed_test::X()))); + EXPECT_EQ("123", StrFormat("%s", FormatStreamed(StreamFormat("%d", 123)))); +} + +// Helper class that creates a temporary file and exposes a FILE* to it. +// It will close the file on destruction. +class TempFile { + public: + TempFile() : file_(std::tmpfile()) {} + ~TempFile() { std::fclose(file_); } + + std::FILE* file() const { return file_; } + + // Read the file into a std::string. + std::string ReadFile() { + std::fseek(file_, 0, SEEK_END); + int size = std::ftell(file_); + EXPECT_GT(size, 0); + std::rewind(file_); + std::string str(2 * size, ' '); + int read_bytes = std::fread(&str[0], 1, str.size(), file_); + EXPECT_EQ(read_bytes, size); + str.resize(read_bytes); + EXPECT_TRUE(std::feof(file_)); + return str; + } + + private: + std::FILE* file_; +}; + +TEST_F(FormatEntryPointTest, FPrintF) { + TempFile tmp; + int result = + FPrintF(tmp.file(), "STRING: %s NUMBER: %010d", std::string("ABC"), -19); + EXPECT_EQ(result, 30); + EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); +} + +TEST_F(FormatEntryPointTest, FPrintFError) { + errno = 0; + int result = FPrintF(stdin, "ABC"); + EXPECT_LT(result, 0); + EXPECT_EQ(errno, EBADF); +} + +#if __GLIBC__ +TEST_F(FormatEntryPointTest, FprintfTooLarge) { + std::FILE* f = std::fopen("/dev/null", "w"); + int width = 2000000000; + errno = 0; + int result = FPrintF(f, "%*d %*d", width, 0, width, 0); + EXPECT_LT(result, 0); + EXPECT_EQ(errno, EFBIG); + std::fclose(f); +} + +TEST_F(FormatEntryPointTest, PrintF) { + int stdout_tmp = dup(STDOUT_FILENO); + + TempFile tmp; + std::fflush(stdout); + dup2(fileno(tmp.file()), STDOUT_FILENO); + + int result = PrintF("STRING: %s NUMBER: %010d", std::string("ABC"), -19); + + std::fflush(stdout); + dup2(stdout_tmp, STDOUT_FILENO); + close(stdout_tmp); + + EXPECT_EQ(result, 30); + EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); +} +#endif // __GLIBC__ + +TEST_F(FormatEntryPointTest, SNPrintF) { + char buffer[16]; + int result = + SNPrintF(buffer, sizeof(buffer), "STRING: %s", std::string("ABC")); + EXPECT_EQ(result, 11); + EXPECT_EQ(std::string(buffer), "STRING: ABC"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 123456); + EXPECT_EQ(result, 14); + EXPECT_EQ(std::string(buffer), "NUMBER: 123456"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 1234567); + EXPECT_EQ(result, 15); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 12345678); + EXPECT_EQ(result, 16); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 123456789); + EXPECT_EQ(result, 17); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + result = SNPrintF(nullptr, 0, "Just checking the %s of the output.", "size"); + EXPECT_EQ(result, 37); +} + +TEST(StrFormat, BehavesAsDocumented) { + std::string s = absl::StrFormat("%s, %d!", "Hello", 123); + EXPECT_EQ("Hello, 123!", s); + // The format of a replacement is + // '%'[position][flags][width['.'precision]][length_modifier][format] + EXPECT_EQ(absl::StrFormat("%1$+3.2Lf", 1.1), "+1.10"); + // Text conversion: + // "c" - Character. Eg: 'a' -> "A", 20 -> " " + EXPECT_EQ(StrFormat("%c", 'a'), "a"); + EXPECT_EQ(StrFormat("%c", 0x20), " "); + // Formats char and integral types: int, long, uint64_t, etc. + EXPECT_EQ(StrFormat("%c", int{'a'}), "a"); + EXPECT_EQ(StrFormat("%c", long{'a'}), "a"); // NOLINT + EXPECT_EQ(StrFormat("%c", uint64_t{'a'}), "a"); + // "s" - std::string Eg: "C" -> "C", std::string("C++") -> "C++" + // Formats std::string, char*, string_view, and Cord. + EXPECT_EQ(StrFormat("%s", "C"), "C"); + EXPECT_EQ(StrFormat("%s", std::string("C++")), "C++"); + EXPECT_EQ(StrFormat("%s", string_view("view")), "view"); + // Integral Conversion + // These format integral types: char, int, long, uint64_t, etc. + EXPECT_EQ(StrFormat("%d", char{10}), "10"); + EXPECT_EQ(StrFormat("%d", int{10}), "10"); + EXPECT_EQ(StrFormat("%d", long{10}), "10"); // NOLINT + EXPECT_EQ(StrFormat("%d", uint64_t{10}), "10"); + // d,i - signed decimal Eg: -10 -> "-10" + EXPECT_EQ(StrFormat("%d", -10), "-10"); + EXPECT_EQ(StrFormat("%i", -10), "-10"); + // o - octal Eg: 10 -> "12" + EXPECT_EQ(StrFormat("%o", 10), "12"); + // u - unsigned decimal Eg: 10 -> "10" + EXPECT_EQ(StrFormat("%u", 10), "10"); + // x/X - lower,upper case hex Eg: 10 -> "a"/"A" + EXPECT_EQ(StrFormat("%x", 10), "a"); + EXPECT_EQ(StrFormat("%X", 10), "A"); + // Floating-point, with upper/lower-case output. + // These format floating points types: float, double, long double, etc. + EXPECT_EQ(StrFormat("%.1f", float{1}), "1.0"); + EXPECT_EQ(StrFormat("%.1f", double{1}), "1.0"); + const long double long_double = 1.0; + EXPECT_EQ(StrFormat("%.1f", long_double), "1.0"); + // These also format integral types: char, int, long, uint64_t, etc.: + EXPECT_EQ(StrFormat("%.1f", char{1}), "1.0"); + EXPECT_EQ(StrFormat("%.1f", int{1}), "1.0"); + EXPECT_EQ(StrFormat("%.1f", long{1}), "1.0"); // NOLINT + EXPECT_EQ(StrFormat("%.1f", uint64_t{1}), "1.0"); + // f/F - decimal. Eg: 123456789 -> "123456789.000000" + EXPECT_EQ(StrFormat("%f", 123456789), "123456789.000000"); + EXPECT_EQ(StrFormat("%F", 123456789), "123456789.000000"); + // e/E - exponentiated Eg: .01 -> "1.00000e-2"/"1.00000E-2" + EXPECT_EQ(StrFormat("%e", .01), "1.000000e-02"); + EXPECT_EQ(StrFormat("%E", .01), "1.000000E-02"); + // g/G - exponentiate to fit Eg: .01 -> "0.01", 1e10 ->"1e+10"/"1E+10" + EXPECT_EQ(StrFormat("%g", .01), "0.01"); + EXPECT_EQ(StrFormat("%g", 1e10), "1e+10"); + EXPECT_EQ(StrFormat("%G", 1e10), "1E+10"); + // a/A - lower,upper case hex Eg: -3.0 -> "-0x1.8p+1"/"-0X1.8P+1" + +// On Android platform <=21, there is a regression in hexfloat formatting. +#if !defined(__ANDROID_API__) || __ANDROID_API__ > 21 + EXPECT_EQ(StrFormat("%.1a", -3.0), "-0x1.8p+1"); // .1 to fix MSVC output + EXPECT_EQ(StrFormat("%.1A", -3.0), "-0X1.8P+1"); // .1 to fix MSVC output +#endif + + // Other conversion + int64_t value = 0x7ffdeb4; + auto ptr_value = static_cast<uintptr_t>(value); + const int& something = *reinterpret_cast<const int*>(ptr_value); + EXPECT_EQ(StrFormat("%p", &something), StrFormat("0x%x", ptr_value)); + + // Output widths are supported, with optional flags. + EXPECT_EQ(StrFormat("%3d", 1), " 1"); + EXPECT_EQ(StrFormat("%3d", 123456), "123456"); + EXPECT_EQ(StrFormat("%06.2f", 1.234), "001.23"); + EXPECT_EQ(StrFormat("%+d", 1), "+1"); + EXPECT_EQ(StrFormat("% d", 1), " 1"); + EXPECT_EQ(StrFormat("%-4d", -1), "-1 "); + EXPECT_EQ(StrFormat("%#o", 10), "012"); + EXPECT_EQ(StrFormat("%#x", 15), "0xf"); + EXPECT_EQ(StrFormat("%04d", 8), "0008"); + // Posix positional substitution. + EXPECT_EQ(absl::StrFormat("%2$s, %3$s, %1$s!", "vici", "veni", "vidi"), + "veni, vidi, vici!"); + // Length modifiers are ignored. + EXPECT_EQ(StrFormat("%hhd", int{1}), "1"); + EXPECT_EQ(StrFormat("%hd", int{1}), "1"); + EXPECT_EQ(StrFormat("%ld", int{1}), "1"); + EXPECT_EQ(StrFormat("%lld", int{1}), "1"); + EXPECT_EQ(StrFormat("%Ld", int{1}), "1"); + EXPECT_EQ(StrFormat("%jd", int{1}), "1"); + EXPECT_EQ(StrFormat("%zd", int{1}), "1"); + EXPECT_EQ(StrFormat("%td", int{1}), "1"); + EXPECT_EQ(StrFormat("%qd", int{1}), "1"); +} + +using str_format_internal::ExtendedParsedFormat; +using str_format_internal::ParsedFormatBase; + +struct SummarizeConsumer { + std::string* out; + explicit SummarizeConsumer(std::string* out) : out(out) {} + + bool Append(string_view s) { + *out += "[" + std::string(s) + "]"; + return true; + } + + bool ConvertOne(const str_format_internal::UnboundConversion& conv, + string_view s) { + *out += "{"; + *out += std::string(s); + *out += ":"; + *out += std::to_string(conv.arg_position) + "$"; + if (conv.width.is_from_arg()) { + *out += std::to_string(conv.width.get_from_arg()) + "$*"; + } + if (conv.precision.is_from_arg()) { + *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*"; + } + *out += conv.conv.Char(); + *out += "}"; + return true; + } +}; + +std::string SummarizeParsedFormat(const ParsedFormatBase& pc) { + std::string out; + if (!pc.ProcessFormat(SummarizeConsumer(&out))) out += "!"; + return out; +} + +class ParsedFormatTest : public testing::Test {}; + +TEST_F(ParsedFormatTest, SimpleChecked) { + EXPECT_EQ("[ABC]{d:1$d}[DEF]", + SummarizeParsedFormat(ParsedFormat<'d'>("ABC%dDEF"))); + EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", + SummarizeParsedFormat(ParsedFormat<'s', 'd', 'f'>("%sFFF%dZZZ%f"))); + EXPECT_EQ("{s:1$s}[ ]{.*d:3$.2$*d}", + SummarizeParsedFormat(ParsedFormat<'s', '*', 'd'>("%s %.*d"))); +} + +TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) { + auto f = ParsedFormat<'d'>::New("ABC%dDEF"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); + + std::string format = "%sFFF%dZZZ%f"; + auto f2 = ParsedFormat<'s', 'd', 'f'>::New(format); + + ASSERT_TRUE(f2); + EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); + + f2 = ParsedFormat<'s', 'd', 'f'>::New("%s %d %f"); + + ASSERT_TRUE(f2); + EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); + + auto star = ParsedFormat<'*', 'd'>::New("%*d"); + ASSERT_TRUE(star); + EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star)); + + auto dollar = ParsedFormat<'d', 's'>::New("%2$s %1$d"); + ASSERT_TRUE(dollar); + EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar)); + // with reuse + dollar = ParsedFormat<'d', 's'>::New("%2$s %1$d %1$d"); + ASSERT_TRUE(dollar); + EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}", + SummarizeParsedFormat(*dollar)); +} + +TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) { + EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC"))); + EXPECT_FALSE((ParsedFormat<'d', 's'>::New("%dABC"))); + EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC%2$s"))); + auto f = ParsedFormat<'d', 's'>::NewAllowIgnored("ABC"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); + f = ParsedFormat<'d', 's'>::NewAllowIgnored("%dABC"); + ASSERT_TRUE(f); + EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f)); + f = ParsedFormat<'d', 's'>::NewAllowIgnored("ABC%2$s"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); +} + +TEST_F(ParsedFormatTest, SimpleUncheckedUnsupported) { + EXPECT_FALSE(ParsedFormat<'d'>::New("%1$d %1$x")); + EXPECT_FALSE(ParsedFormat<'x'>::New("%1$d %1$x")); +} + +TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) { + EXPECT_FALSE(ParsedFormat<'d'>::New("")); + + EXPECT_FALSE(ParsedFormat<'d'>::New("ABC%dDEF%d")); + + std::string format = "%sFFF%dZZZ%f"; + EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format))); +} + +using str_format_internal::Conv; + +TEST_F(ParsedFormatTest, UncheckedCorrect) { + auto f = ExtendedParsedFormat<Conv::d>::New("ABC%dDEF"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); + + std::string format = "%sFFF%dZZZ%f"; + auto f2 = + ExtendedParsedFormat<Conv::string, Conv::d, Conv::floating>::New(format); + + ASSERT_TRUE(f2); + EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); + + f2 = ExtendedParsedFormat<Conv::string, Conv::d, Conv::floating>::New( + "%s %d %f"); + + ASSERT_TRUE(f2); + EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); + + auto star = ExtendedParsedFormat<Conv::star, Conv::d>::New("%*d"); + ASSERT_TRUE(star); + EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star)); + + auto dollar = ExtendedParsedFormat<Conv::d, Conv::s>::New("%2$s %1$d"); + ASSERT_TRUE(dollar); + EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar)); + // with reuse + dollar = ExtendedParsedFormat<Conv::d, Conv::s>::New("%2$s %1$d %1$d"); + ASSERT_TRUE(dollar); + EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}", + SummarizeParsedFormat(*dollar)); +} + +TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { + EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC"))); + EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("%dABC"))); + EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC%2$s"))); + auto f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); + f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("%dABC"); + ASSERT_TRUE(f); + EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f)); + f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC%2$s"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); +} + +TEST_F(ParsedFormatTest, UncheckedMultipleTypes) { + auto dx = ExtendedParsedFormat<Conv::d | Conv::x>::New("%1$d %1$x"); + EXPECT_TRUE(dx); + EXPECT_EQ("{1$d:1$d}[ ]{1$x:1$x}", SummarizeParsedFormat(*dx)); + + dx = ExtendedParsedFormat<Conv::d | Conv::x>::New("%1$d"); + EXPECT_TRUE(dx); + EXPECT_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx)); +} + +TEST_F(ParsedFormatTest, UncheckedIncorrect) { + EXPECT_FALSE(ExtendedParsedFormat<Conv::d>::New("")); + + EXPECT_FALSE(ExtendedParsedFormat<Conv::d>::New("ABC%dDEF%d")); + + std::string format = "%sFFF%dZZZ%f"; + EXPECT_FALSE((ExtendedParsedFormat<Conv::s, Conv::d, Conv::g>::New(format))); +} + +TEST_F(ParsedFormatTest, RegressionMixPositional) { + EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::o>::New("%1$d %o"))); +} + +} // namespace +} // inline namespace lts_2018_12_18 +} // namespace absl + +// Some codegen thunks that we can use to easily dump the generated assembly for +// different StrFormat calls. + +std::string CodegenAbslStrFormatInt(int i) { // NOLINT + return absl::StrFormat("%d", i); +} + +std::string CodegenAbslStrFormatIntStringInt64(int i, const std::string& s, + int64_t i64) { // NOLINT + return absl::StrFormat("%d %s %d", i, s, i64); +} + +void CodegenAbslStrAppendFormatInt(std::string* out, int i) { // NOLINT + absl::StrAppendFormat(out, "%d", i); +} + +void CodegenAbslStrAppendFormatIntStringInt64(std::string* out, int i, + const std::string& s, + int64_t i64) { // NOLINT + absl::StrAppendFormat(out, "%d %s %d", i, s, i64); +} diff --git a/absl/strings/str_join.h b/absl/strings/str_join.h index bce2cd66..dc476a22 100644 --- a/absl/strings/str_join.h +++ b/absl/strings/str_join.h @@ -18,10 +18,10 @@ // ----------------------------------------------------------------------------- // // This header file contains functions for joining a range of elements and -// returning the result as a std::string. StrJoin operations are specified by passing -// a range, a separator std::string to use between the elements joined, and an +// returning the result as a string. StrJoin operations are specified by passing +// a range, a separator string to use between the elements joined, and an // optional Formatter responsible for converting each argument in the range to a -// std::string. If omitted, a default `AlphaNumFormatter()` is called on the elements +// string. If omitted, a default `AlphaNumFormatter()` is called on the elements // to be joined, using the same formatting that `absl::StrCat()` uses. This // package defines a number of default formatters, and you can define your own // implementations. @@ -29,7 +29,7 @@ // Ranges are specified by passing a container with `std::begin()` and // `std::end()` iterators, container-specific `begin()` and `end()` iterators, a // brace-initialized `std::initializer_list`, or a `std::tuple` of heterogeneous -// objects. The separator std::string is specified as an `absl::string_view`. +// objects. The separator string is specified as an `absl::string_view`. // // Because the default formatter uses the `absl::AlphaNum` class, // `absl::StrJoin()`, like `absl::StrCat()`, will work out-of-the-box on @@ -37,8 +37,8 @@ // // Example: // -// std::vector<std::string> v = {"foo", "bar", "baz"}; -// std::string s = absl::StrJoin(v, "-"); +// std::vector<string> v = {"foo", "bar", "baz"}; +// string s = absl::StrJoin(v, "-"); // EXPECT_EQ("foo-bar-baz", s); // // See comments on the `absl::StrJoin()` function for more examples. @@ -52,6 +52,7 @@ #include <iterator> #include <string> #include <tuple> +#include <type_traits> #include <utility> #include "absl/base/macros.h" @@ -59,23 +60,23 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // ----------------------------------------------------------------------------- // Concept: Formatter // ----------------------------------------------------------------------------- // // A Formatter is a function object that is responsible for formatting its -// argument as a std::string and appending it to a given output std::string. Formatters +// argument as a string and appending it to a given output string. Formatters // may be implemented as function objects, lambdas, or normal functions. You may // provide your own Formatter to enable `absl::StrJoin()` to work with arbitrary // types. // // The following is an example of a custom Formatter that simply uses -// `std::to_string()` to format an integer as a std::string. +// `std::to_string()` to format an integer as a string. // // struct MyFormatter { -// void operator()(std::string* out, int i) const { +// void operator()(string* out, int i) const { // out->append(std::to_string(i)); // } // }; @@ -84,7 +85,7 @@ inline namespace lts_2018_06_20 { // argument to `absl::StrJoin()`: // // std::vector<int> v = {1, 2, 3, 4}; -// std::string s = absl::StrJoin(v, "-", MyFormatter()); +// string s = absl::StrJoin(v, "-", MyFormatter()); // EXPECT_EQ("1-2-3-4", s); // // The following standard formatters are provided within this file: @@ -156,10 +157,10 @@ DereferenceFormatter() { // StrJoin() // ----------------------------------------------------------------------------- // -// Joins a range of elements and returns the result as a std::string. -// `absl::StrJoin()` takes a range, a separator std::string to use between the +// Joins a range of elements and returns the result as a string. +// `absl::StrJoin()` takes a range, a separator string to use between the // elements joined, and an optional Formatter responsible for converting each -// argument in the range to a std::string. +// argument in the range to a string. // // If omitted, the default `AlphaNumFormatter()` is called on the elements to be // joined. @@ -167,22 +168,22 @@ DereferenceFormatter() { // Example 1: // // Joins a collection of strings. This pattern also works with a collection // // of `absl::string_view` or even `const char*`. -// std::vector<std::string> v = {"foo", "bar", "baz"}; -// std::string s = absl::StrJoin(v, "-"); +// std::vector<string> v = {"foo", "bar", "baz"}; +// string s = absl::StrJoin(v, "-"); // EXPECT_EQ("foo-bar-baz", s); // // Example 2: // // Joins the values in the given `std::initializer_list<>` specified using // // brace initialization. This pattern also works with an initializer_list // // of ints or `absl::string_view` -- any `AlphaNum`-compatible type. -// std::string s = absl::StrJoin({"foo", "bar", "baz"}, "-"); +// string s = absl::StrJoin({"foo", "bar", "baz"}, "-"); // EXPECT_EQ("foo-bar-baz", s); // // Example 3: // // Joins a collection of ints. This pattern also works with floats, // // doubles, int64s -- any `StrCat()`-compatible type. // std::vector<int> v = {1, 2, 3, -4}; -// std::string s = absl::StrJoin(v, "-"); +// string s = absl::StrJoin(v, "-"); // EXPECT_EQ("1-2-3--4", s); // // Example 4: @@ -193,7 +194,7 @@ DereferenceFormatter() { // // `std::vector<int*>`. // int x = 1, y = 2, z = 3; // std::vector<int*> v = {&x, &y, &z}; -// std::string s = absl::StrJoin(v, "-"); +// string s = absl::StrJoin(v, "-"); // EXPECT_EQ("1-2-3", s); // // Example 5: @@ -202,42 +203,42 @@ DereferenceFormatter() { // v.emplace_back(new int(1)); // v.emplace_back(new int(2)); // v.emplace_back(new int(3)); -// std::string s = absl::StrJoin(v, "-"); +// string s = absl::StrJoin(v, "-"); // EXPECT_EQ("1-2-3", s); // // Example 6: // // Joins a `std::map`, with each key-value pair separated by an equals // // sign. This pattern would also work with, say, a // // `std::vector<std::pair<>>`. -// std::map<std::string, int> m = { +// std::map<string, int> m = { // std::make_pair("a", 1), // std::make_pair("b", 2), // std::make_pair("c", 3)}; -// std::string s = absl::StrJoin(m, ",", absl::PairFormatter("=")); +// string s = absl::StrJoin(m, ",", absl::PairFormatter("=")); // EXPECT_EQ("a=1,b=2,c=3", s); // // Example 7: // // These examples show how `absl::StrJoin()` handles a few common edge // // cases: -// std::vector<std::string> v_empty; +// std::vector<string> v_empty; // EXPECT_EQ("", absl::StrJoin(v_empty, "-")); // -// std::vector<std::string> v_one_item = {"foo"}; +// std::vector<string> v_one_item = {"foo"}; // EXPECT_EQ("foo", absl::StrJoin(v_one_item, "-")); // -// std::vector<std::string> v_empty_string = {""}; +// std::vector<string> v_empty_string = {""}; // EXPECT_EQ("", absl::StrJoin(v_empty_string, "-")); // -// std::vector<std::string> v_one_item_empty_string = {"a", ""}; +// std::vector<string> v_one_item_empty_string = {"a", ""}; // EXPECT_EQ("a-", absl::StrJoin(v_one_item_empty_string, "-")); // -// std::vector<std::string> v_two_empty_string = {"", ""}; +// std::vector<string> v_two_empty_string = {"", ""}; // EXPECT_EQ("-", absl::StrJoin(v_two_empty_string, "-")); // // Example 8: // // Joins a `std::tuple<T...>` of heterogeneous types, converting each to -// // a std::string using the `absl::AlphaNum` class. -// std::string s = absl::StrJoin(std::make_tuple(123, "abc", 0.456), "-"); +// // a string using the `absl::AlphaNum` class. +// string s = absl::StrJoin(std::make_tuple(123, "abc", 0.456), "-"); // EXPECT_EQ("123-abc-0.456", s); template <typename Iterator, typename Formatter> @@ -284,7 +285,7 @@ std::string StrJoin(const std::tuple<T...>& value, absl::string_view separator) return strings_internal::JoinAlgorithm(value, separator, AlphaNumFormatter()); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_STR_JOIN_H_ diff --git a/absl/strings/str_replace.cc b/absl/strings/str_replace.cc index c50d314a..72a0b584 100644 --- a/absl/strings/str_replace.cc +++ b/absl/strings/str_replace.cc @@ -17,7 +17,7 @@ #include "absl/strings/str_cat.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace strings_internal { using FixedMapping = @@ -77,5 +77,5 @@ int StrReplaceAll(strings_internal::FixedMapping replacements, std::string* targ return StrReplaceAll<strings_internal::FixedMapping>(replacements, target); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/str_replace.h b/absl/strings/str_replace.h index 7ba43946..a963f91e 100644 --- a/absl/strings/str_replace.h +++ b/absl/strings/str_replace.h @@ -17,19 +17,19 @@ // File: str_replace.h // ----------------------------------------------------------------------------- // -// This file defines `absl::StrReplaceAll()`, a general-purpose std::string +// This file defines `absl::StrReplaceAll()`, a general-purpose string // replacement function designed for large, arbitrary text substitutions, // especially on strings which you are receiving from some other system for // further processing (e.g. processing regular expressions, escaping HTML -// entities, etc. `StrReplaceAll` is designed to be efficient even when only +// entities, etc.). `StrReplaceAll` is designed to be efficient even when only // one substitution is being performed, or when substitution is rare. // -// If the std::string being modified is known at compile-time, and the substitutions +// If the string being modified is known at compile-time, and the substitutions // vary, `absl::Substitute()` may be a better choice. // // Example: // -// std::string html_escaped = absl::StrReplaceAll(user_input, { +// string html_escaped = absl::StrReplaceAll(user_input, { // {"&", "&"}, // {"<", "<"}, // {">", ">"}, @@ -46,20 +46,20 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // StrReplaceAll() // -// Replaces character sequences within a given std::string with replacements provided +// Replaces character sequences within a given string with replacements provided // within an initializer list of key/value pairs. Candidate replacements are -// considered in order as they occur within the std::string, with earlier matches +// considered in order as they occur within the string, with earlier matches // taking precedence, and longer matches taking precedence for candidates -// starting at the same position in the std::string. Once a substitution is made, the +// starting at the same position in the string. Once a substitution is made, the // replaced text is not considered for any further substitutions. // // Example: // -// std::string s = absl::StrReplaceAll("$who bought $count #Noun. Thanks $who!", +// string s = absl::StrReplaceAll("$who bought $count #Noun. Thanks $who!", // {{"$count", absl::StrCat(5)}, // {"$who", "Bob"}, // {"#Noun", "Apples"}}); @@ -79,28 +79,28 @@ ABSL_MUST_USE_RESULT std::string StrReplaceAll( // replacements["$who"] = "Bob"; // replacements["$count"] = "5"; // replacements["#Noun"] = "Apples"; -// std::string s = absl::StrReplaceAll("$who bought $count #Noun. Thanks $who!", +// string s = absl::StrReplaceAll("$who bought $count #Noun. Thanks $who!", // replacements); // EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); // // // A std::vector of std::pair elements can be more efficient. -// std::vector<std::pair<const absl::string_view, std::string>> replacements; +// std::vector<std::pair<const absl::string_view, string>> replacements; // replacements.push_back({"&", "&"}); // replacements.push_back({"<", "<"}); // replacements.push_back({">", ">"}); -// std::string s = absl::StrReplaceAll("if (ptr < &foo)", +// string s = absl::StrReplaceAll("if (ptr < &foo)", // replacements); // EXPECT_EQ("if (ptr < &foo)", s); template <typename StrToStrMapping> std::string StrReplaceAll(absl::string_view s, const StrToStrMapping& replacements); // Overload of `StrReplaceAll()` to replace character sequences within a given -// output std::string *in place* with replacements provided within an initializer +// output string *in place* with replacements provided within an initializer // list of key/value pairs, returning the number of substitutions that occurred. // // Example: // -// std::string s = std::string("$who bought $count #Noun. Thanks $who!"); +// string s = std::string("$who bought $count #Noun. Thanks $who!"); // int count; // count = absl::StrReplaceAll({{"$count", absl::StrCat(5)}, // {"$who", "Bob"}, @@ -113,12 +113,12 @@ int StrReplaceAll( std::string* target); // Overload of `StrReplaceAll()` to replace patterns within a given output -// std::string *in place* with replacements provided within a container of key/value +// string *in place* with replacements provided within a container of key/value // pairs. // // Example: // -// std::string s = std::string("if (ptr < &foo)"); +// string s = std::string("if (ptr < &foo)"); // int count = absl::StrReplaceAll({{"&", "&"}, // {"<", "<"}, // {">", ">"}}, &s); @@ -209,7 +209,7 @@ int StrReplaceAll(const StrToStrMapping& replacements, std::string* target) { return substitutions; } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_STR_REPLACE_H_ diff --git a/absl/strings/str_replace_benchmark.cc b/absl/strings/str_replace_benchmark.cc index e608de8d..8386f2e6 100644 --- a/absl/strings/str_replace_benchmark.cc +++ b/absl/strings/str_replace_benchmark.cc @@ -38,16 +38,16 @@ struct Replacement { {"liquor", "shakes"}, // }; -// Here, we set up a std::string for use in global-replace benchmarks. +// Here, we set up a string for use in global-replace benchmarks. // We started with a million blanks, and then deterministically insert -// 10,000 copies each of two pangrams. The result is a std::string that is +// 10,000 copies each of two pangrams. The result is a string that is // 40% blank space and 60% these words. 'the' occurs 18,247 times and // all the substitutions together occur 49,004 times. // -// We then create "after_replacing_the" to be a std::string that is a result of +// We then create "after_replacing_the" to be a string that is a result of // replacing "the" with "box" in big_string. // -// And then we create "after_replacing_many" to be a std::string that is result +// And then we create "after_replacing_many" to be a string that is result // of preferring several substitutions. void SetUpStrings() { if (big_string == nullptr) { diff --git a/absl/strings/str_split.cc b/absl/strings/str_split.cc index 2a31e09e..cd90425f 100644 --- a/absl/strings/str_split.cc +++ b/absl/strings/str_split.cc @@ -27,7 +27,7 @@ #include "absl/strings/ascii.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace { @@ -44,10 +44,11 @@ absl::string_view GenericFind(absl::string_view text, if (delimiter.empty() && text.length() > 0) { // Special case for empty std::string delimiters: always return a zero-length // absl::string_view referring to the item at position 1 past pos. - return absl::string_view(text.begin() + pos + 1, 0); + return absl::string_view(text.data() + pos + 1, 0); } size_t found_pos = absl::string_view::npos; - absl::string_view found(text.end(), 0); // By default, not found + absl::string_view found(text.data() + text.size(), + 0); // By default, not found found_pos = find_policy.Find(text, delimiter, pos); if (found_pos != absl::string_view::npos) { found = absl::string_view(text.data() + found_pos, @@ -88,7 +89,7 @@ absl::string_view ByString::Find(absl::string_view text, size_t pos) const { // absl::string_view. size_t found_pos = text.find(delimiter_[0], pos); if (found_pos == absl::string_view::npos) - return absl::string_view(text.end(), 0); + return absl::string_view(text.data() + text.size(), 0); return text.substr(found_pos, 1); } return GenericFind(text, delimiter_, pos, LiteralPolicy()); @@ -101,7 +102,7 @@ absl::string_view ByString::Find(absl::string_view text, size_t pos) const { absl::string_view ByChar::Find(absl::string_view text, size_t pos) const { size_t found_pos = text.find(c_, pos); if (found_pos == absl::string_view::npos) - return absl::string_view(text.end(), 0); + return absl::string_view(text.data() + text.size(), 0); return text.substr(found_pos, 1); } @@ -129,10 +130,10 @@ absl::string_view ByLength::Find(absl::string_view text, // If the std::string is shorter than the chunk size we say we // "can't find the delimiter" so this will be the last chunk. if (substr.length() <= static_cast<size_t>(length_)) - return absl::string_view(text.end(), 0); + return absl::string_view(text.data() + text.size(), 0); - return absl::string_view(substr.begin() + length_, 0); + return absl::string_view(substr.data() + length_, 0); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/str_split.h b/absl/strings/str_split.h index b9f586b9..9483b30e 100644 --- a/absl/strings/str_split.h +++ b/absl/strings/str_split.h @@ -19,13 +19,13 @@ // // This file contains functions for splitting strings. It defines the main // `StrSplit()` function, several delimiters for determining the boundaries on -// which to split the std::string, and predicates for filtering delimited results. +// which to split the string, and predicates for filtering delimited results. // `StrSplit()` adapts the returned collection to the type specified by the // caller. // // Example: // -// // Splits the given std::string on commas. Returns the results in a +// // Splits the given string on commas. Returns the results in a // // vector of strings. // std::vector<std::string> v = absl::StrSplit("a,b,c", ','); // // Can also use "," @@ -49,14 +49,14 @@ #include "absl/strings/strip.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { //------------------------------------------------------------------------------ // Delimiters //------------------------------------------------------------------------------ // // `StrSplit()` uses delimiters to define the boundaries between elements in the -// provided input. Several `Delimiter` types are defined below. If a std::string +// provided input. Several `Delimiter` types are defined below. If a string // (`const char*`, `std::string`, or `absl::string_view`) is passed in place of // an explicit `Delimiter` object, `StrSplit()` treats it the same way as if it // were passed a `ByString` delimiter. @@ -66,7 +66,7 @@ inline namespace lts_2018_06_20 { // // The following `Delimiter` types are available for use within `StrSplit()`: // -// - `ByString` (default for std::string arguments) +// - `ByString` (default for string arguments) // - `ByChar` (default for a char argument) // - `ByAnyChar` // - `ByLength` @@ -77,15 +77,15 @@ inline namespace lts_2018_06_20 { // be split and the position to begin searching for the next delimiter in the // input text. The returned absl::string_view should refer to the next // occurrence (after pos) of the represented delimiter; this returned -// absl::string_view represents the next location where the input std::string should +// absl::string_view represents the next location where the input string should // be broken. The returned absl::string_view may be zero-length if the Delimiter -// does not represent a part of the std::string (e.g., a fixed-length delimiter). If +// does not represent a part of the string (e.g., a fixed-length delimiter). If // no delimiter is found in the given text, a zero-length absl::string_view // referring to text.end() should be returned (e.g., // absl::string_view(text.end(), 0)). It is important that the returned // absl::string_view always be within the bounds of input text given as an -// argument--it must not refer to a std::string that is physically located outside of -// the given std::string. +// argument--it must not refer to a string that is physically located outside of +// the given string. // // The following example is a simple Delimiter object that is created with a // single char and will look for that char in the text passed to the Find() @@ -105,13 +105,13 @@ inline namespace lts_2018_06_20 { // ByString // -// A sub-std::string delimiter. If `StrSplit()` is passed a std::string in place of a -// `Delimiter` object, the std::string will be implicitly converted into a +// A sub-string delimiter. If `StrSplit()` is passed a string in place of a +// `Delimiter` object, the string will be implicitly converted into a // `ByString` delimiter. // // Example: // -// // Because a std::string literal is converted to an `absl::ByString`, +// // Because a string literal is converted to an `absl::ByString`, // // the following two splits are equivalent. // // std::vector<std::string> v1 = absl::StrSplit("a, b, c", ", "); @@ -132,7 +132,7 @@ class ByString { // ByChar // // A single character delimiter. `ByChar` is functionally equivalent to a -// 1-char std::string within a `ByString` delimiter, but slightly more +// 1-char string within a `ByString` delimiter, but slightly more // efficient. // // Example: @@ -165,9 +165,9 @@ class ByChar { // ByAnyChar // // A delimiter that will match any of the given byte-sized characters within -// its provided std::string. +// its provided string. // -// Note: this delimiter works with single-byte std::string data, but does not work +// Note: this delimiter works with single-byte string data, but does not work // with variable-width encodings, such as UTF-8. // // Example: @@ -176,8 +176,8 @@ class ByChar { // std::vector<std::string> v = absl::StrSplit("a,b=c", ByAnyChar(",=")); // // v[0] == "a", v[1] == "b", v[2] == "c" // -// If `ByAnyChar` is given the empty std::string, it behaves exactly like -// `ByString` and matches each individual character in the input std::string. +// If `ByAnyChar` is given the empty string, it behaves exactly like +// `ByString` and matches each individual character in the input string. // class ByAnyChar { public: @@ -193,7 +193,7 @@ class ByAnyChar { // A delimiter for splitting into equal-length strings. The length argument to // the constructor must be greater than 0. // -// Note: this delimiter works with single-byte std::string data, but does not work +// Note: this delimiter works with single-byte string data, but does not work // with variable-width encodings, such as UTF-8. // // Example: @@ -203,7 +203,7 @@ class ByAnyChar { // // v[0] == "123", v[1] == "456", v[2] == "789" // -// Note that the std::string does not have to be a multiple of the fixed split +// Note that the string does not have to be a multiple of the fixed split // length. In such a case, the last substring will be shorter. // // using absl::ByLength; @@ -224,9 +224,9 @@ namespace strings_internal { // A traits-like metafunction for selecting the default Delimiter object type // for a particular Delimiter type. The base case simply exposes type Delimiter // itself as the delimiter's Type. However, there are specializations for -// std::string-like objects that map them to the ByString delimiter object. +// string-like objects that map them to the ByString delimiter object. // This allows functions like absl::StrSplit() and absl::MaxSplits() to accept -// std::string-like objects (e.g., ',') as delimiter arguments but they will be +// string-like objects (e.g., ',') as delimiter arguments but they will be // treated as if a ByString delimiter was given. template <typename Delimiter> struct SelectDelimiter { @@ -262,7 +262,8 @@ class MaxSplitsImpl { : delimiter_(delimiter), limit_(limit), count_(0) {} absl::string_view Find(absl::string_view text, size_t pos) { if (count_++ == limit_) { - return absl::string_view(text.end(), 0); // No more matches. + return absl::string_view(text.data() + text.size(), + 0); // No more matches. } return delimiter_.Find(text, pos); } @@ -332,7 +333,7 @@ struct AllowEmpty { // SkipEmpty() // // Returns `false` if the given `absl::string_view` is empty, indicating that -// `StrSplit()` should omit the empty std::string. +// `StrSplit()` should omit the empty string. // // Example: // @@ -340,7 +341,7 @@ struct AllowEmpty { // // // v[0] == "a", v[1] == "b" // -// Note: `SkipEmpty()` does not consider a std::string containing only whitespace +// Note: `SkipEmpty()` does not consider a string containing only whitespace // to be empty. To skip such whitespace as well, use the `SkipWhitespace()` // predicate. struct SkipEmpty { @@ -350,7 +351,7 @@ struct SkipEmpty { // SkipWhitespace() // // Returns `false` if the given `absl::string_view` is empty *or* contains only -// whitespace, indicating that `StrSplit()` should omit the std::string. +// whitespace, indicating that `StrSplit()` should omit the string. // // Example: // @@ -374,11 +375,11 @@ struct SkipWhitespace { // StrSplit() // -// Splits a given `std::string` based on the provided `Delimiter` object, -// returning the elements within the type specified by the caller. Optionally, -// you may also pass a `Predicate` to `StrSplit()` indicating whether to include -// or exclude the resulting element within the final result set. (See the -// overviews for Delimiters and Predicates above.) +// Splits a given string based on the provided `Delimiter` object, returning the +// elements within the type specified by the caller. Optionally, you may pass a +// `Predicate` to `StrSplit()` indicating whether to include or exclude the +// resulting element within the final result set. (See the overviews for +// Delimiters and Predicates above.) // // Example: // @@ -413,7 +414,7 @@ struct SkipWhitespace { // // The `StrSplit()` function adapts the returned collection to the collection // specified by the caller (e.g. `std::vector` above). The returned collections -// may contain `string`, `absl::string_view` (in which case the original std::string +// may contain `string`, `absl::string_view` (in which case the original string // being split must ensure that it outlives the collection), or any object that // can be explicitly created from an `absl::string_view`. This behavior works // for: @@ -425,7 +426,7 @@ struct SkipWhitespace { // Example: // // // The results are returned as `absl::string_view` objects. Note that we -// // have to ensure that the input std::string outlives any results. +// // have to ensure that the input string outlives any results. // std::vector<absl::string_view> v = absl::StrSplit("a,b,c", ','); // // // Stores results in a std::set<std::string>, which also performs @@ -445,7 +446,7 @@ struct SkipWhitespace { // // is provided as a series of key/value pairs. For example, the 0th element // // resulting from the split will be stored as a key to the 1st element. If // // an odd number of elements are resolved, the last element is paired with -// // a default-constructed value (e.g., empty std::string). +// // a default-constructed value (e.g., empty string). // std::map<std::string, std::string> m = absl::StrSplit("a,b,c", ','); // // m["a"] == "b", m["c"] == "" // last component value equals "" // @@ -453,14 +454,14 @@ struct SkipWhitespace { // elements and is not a collection type. When splitting to a `std::pair` the // first two split strings become the `std::pair` `.first` and `.second` // members, respectively. The remaining split substrings are discarded. If there -// are less than two split substrings, the empty std::string is used for the +// are less than two split substrings, the empty string is used for the // corresponding // `std::pair` member. // // Example: // // // Stores first two split strings as the members in a std::pair. -// std::pair<std::string, std::string> p = absl::StrSplit("a,b,c", ','); +// std::pair<string, string> p = absl::StrSplit("a,b,c", ','); // // p.first == "a", p.second == "b" // "c" is omitted. // // The `StrSplit()` function can be used multiple times to perform more @@ -468,9 +469,9 @@ struct SkipWhitespace { // // Example: // -// // The input std::string "a=b=c,d=e,f=,g" becomes +// // The input string "a=b=c,d=e,f=,g" becomes // // { "a" => "b=c", "d" => "e", "f" => "", "g" => "" } -// std::map<std::string, std::string> m; +// std::map<string, string> m; // for (absl::string_view sp : absl::StrSplit("a=b=c,d=e,f=,g", ',')) { // m.insert(absl::StrSplit(sp, absl::MaxSplits('=', 1))); // } @@ -507,7 +508,7 @@ StrSplit(strings_internal::ConvertibleToStringView text, Delimiter d, std::move(text), DelimiterType(d), std::move(p)); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_STR_SPLIT_H_ diff --git a/absl/strings/str_split_benchmark.cc b/absl/strings/str_split_benchmark.cc index 326ff744..0ac297c8 100644 --- a/absl/strings/str_split_benchmark.cc +++ b/absl/strings/str_split_benchmark.cc @@ -35,16 +35,16 @@ std::string MakeTestString(int desired_length) { return test; } -void BM_Split2StringPiece(benchmark::State& state) { +void BM_Split2StringView(benchmark::State& state) { std::string test = MakeTestString(state.range(0)); for (auto _ : state) { std::vector<absl::string_view> result = absl::StrSplit(test, ';'); benchmark::DoNotOptimize(result); } } -BENCHMARK_RANGE(BM_Split2StringPiece, 0, 1 << 20); +BENCHMARK_RANGE(BM_Split2StringView, 0, 1 << 20); -void BM_Split2StringPieceLifted(benchmark::State& state) { +void BM_Split2StringViewLifted(benchmark::State& state) { std::string test = MakeTestString(state.range(0)); std::vector<absl::string_view> result; for (auto _ : state) { @@ -52,7 +52,7 @@ void BM_Split2StringPieceLifted(benchmark::State& state) { } benchmark::DoNotOptimize(result); } -BENCHMARK_RANGE(BM_Split2StringPieceLifted, 0, 1 << 20); +BENCHMARK_RANGE(BM_Split2StringViewLifted, 0, 1 << 20); void BM_Split2String(benchmark::State& state) { std::string test = MakeTestString(state.range(0)); diff --git a/absl/strings/str_split_test.cc b/absl/strings/str_split_test.cc index c172a762..caa88277 100644 --- a/absl/strings/str_split_test.cc +++ b/absl/strings/str_split_test.cc @@ -37,6 +37,34 @@ using ::testing::ElementsAre; using ::testing::Pair; using ::testing::UnorderedElementsAre; +TEST(Split, TraitsTest) { + static_assert(!absl::strings_internal::SplitterIsConvertibleTo<int>::value, + ""); + static_assert(!absl::strings_internal::SplitterIsConvertibleTo<std::string>::value, + ""); + static_assert(absl::strings_internal::SplitterIsConvertibleTo< + std::vector<std::string>>::value, + ""); + static_assert( + !absl::strings_internal::SplitterIsConvertibleTo<std::vector<int>>::value, + ""); + static_assert(absl::strings_internal::SplitterIsConvertibleTo< + std::vector<absl::string_view>>::value, + ""); + static_assert(absl::strings_internal::SplitterIsConvertibleTo< + std::map<std::string, std::string>>::value, + ""); + static_assert(absl::strings_internal::SplitterIsConvertibleTo< + std::map<absl::string_view, absl::string_view>>::value, + ""); + static_assert(!absl::strings_internal::SplitterIsConvertibleTo< + std::map<int, std::string>>::value, + ""); + static_assert(!absl::strings_internal::SplitterIsConvertibleTo< + std::map<std::string, int>>::value, + ""); +} + // This tests the overall split API, which is made up of the absl::StrSplit() // function and the Delimiter objects in the absl:: namespace. // This TEST macro is outside of any namespace to require full specification of @@ -248,7 +276,7 @@ TEST(SplitIterator, Basics) { EXPECT_EQ(it, end); } -// Simple Predicate to skip a particular std::string. +// Simple Predicate to skip a particular string. class Skip { public: explicit Skip(const std::string& s) : s_(s) {} @@ -735,12 +763,12 @@ template <typename Delimiter> static bool IsFoundAtStartingPos(absl::string_view text, Delimiter d, size_t starting_pos, int expected_pos) { absl::string_view found = d.Find(text, starting_pos); - return found.data() != text.end() && + return found.data() != text.data() + text.size() && expected_pos == found.data() - text.data(); } // Helper function for testing Delimiter objects. Returns true if the given -// Delimiter is found in the given std::string at the given position. This function +// Delimiter is found in the given string at the given position. This function // tests two cases: // 1. The actual text given, staring at position 0 // 2. The text given with leading padding that should be ignored diff --git a/absl/strings/string_view.cc b/absl/strings/string_view.cc index 92197a75..3620ff44 100644 --- a/absl/strings/string_view.cc +++ b/absl/strings/string_view.cc @@ -24,7 +24,7 @@ #include "absl/strings/internal/memutil.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace { void WritePadding(std::ostream& o, size_t pad) { @@ -241,7 +241,7 @@ constexpr string_view::size_type string_view::npos; ABSL_STRING_VIEW_SELECTANY constexpr string_view::size_type string_view::kMaxSize; -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_HAVE_STD_STRING_VIEW diff --git a/absl/strings/string_view.h b/absl/strings/string_view.h index 4a5e4d8a..df6f1ae4 100644 --- a/absl/strings/string_view.h +++ b/absl/strings/string_view.h @@ -19,7 +19,7 @@ // // This file contains the definition of the `absl::string_view` class. A // `string_view` points to a contiguous span of characters, often part or all of -// another `std::string`, double-quoted std::string literal, character array, or even +// another `std::string`, double-quoted string literal, character array, or even // another `string_view`. // // This `absl::string_view` abstraction is designed to be a drop-in @@ -35,9 +35,9 @@ #include <string_view> namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { using std::string_view; -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #else // ABSL_HAVE_STD_STRING_VIEW @@ -55,19 +55,19 @@ using std::string_view; #include "absl/base/port.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // absl::string_view // -// A `string_view` provides a lightweight view into the std::string data provided by -// a `std::string`, double-quoted std::string literal, character array, or even -// another `string_view`. A `string_view` does *not* own the std::string to which it +// A `string_view` provides a lightweight view into the string data provided by +// a `std::string`, double-quoted string literal, character array, or even +// another `string_view`. A `string_view` does *not* own the string to which it // points, and that data cannot be modified through the view. // // You can use `string_view` as a function or method parameter anywhere a -// parameter can receive a double-quoted std::string literal, `const char*`, +// parameter can receive a double-quoted string literal, `const char*`, // `std::string`, or another `absl::string_view` argument with no need to copy -// the std::string data. Systematic use of `string_view` within function arguments +// the string data. Systematic use of `string_view` within function arguments // reduces data copies and `strlen()` calls. // // Because of its small size, prefer passing `string_view` by value: @@ -100,8 +100,8 @@ inline namespace lts_2018_06_20 { // `string_view` this way, it is your responsibility to ensure that the object // pointed to by the `string_view` outlives the `string_view`. // -// A `string_view` may represent a whole std::string or just part of a std::string. For -// example, when splitting a std::string, `std::vector<absl::string_view>` is a +// A `string_view` may represent a whole string or just part of a string. For +// example, when splitting a string, `std::vector<absl::string_view>` is a // natural data type for the output. // // @@ -144,7 +144,7 @@ inline namespace lts_2018_06_20 { // All empty `string_view` objects whether null or not, are equal: // // absl::string_view() == absl::string_view("", 0) -// absl::string_view(nullptr, 0) == absl:: string_view("abcdef"+6, 0) +// absl::string_view(nullptr, 0) == absl::string_view("abcdef"+6, 0) class string_view { public: using traits_type = std::char_traits<char>; @@ -176,8 +176,19 @@ class string_view { // Implicit constructor of a `string_view` from nul-terminated `str`. When // accepting possibly null strings, use `absl::NullSafeStringView(str)` // instead (see below). +#if ABSL_HAVE_BUILTIN(__builtin_strlen) || \ + (defined(__GNUC__) && !defined(__clang__)) + // GCC has __builtin_strlen according to + // https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Other-Builtins.html, but + // ABSL_HAVE_BUILTIN doesn't detect that, so we use the extra checks above. + // __builtin_strlen is constexpr. + constexpr string_view(const char* str) // NOLINT(runtime/explicit) + : ptr_(str), + length_(CheckLengthInternal(str ? __builtin_strlen(str) : 0)) {} +#else constexpr string_view(const char* str) // NOLINT(runtime/explicit) - : ptr_(str), length_(CheckLengthInternal(StrLenInternal(str))) {} + : ptr_(str), length_(CheckLengthInternal(str ? strlen(str) : 0)) {} +#endif // Implicit constructor of a `string_view` from a `const char*` and length. constexpr string_view(const char* data, size_type len) @@ -343,7 +354,7 @@ class string_view { // // Returns a "substring" of the `string_view` (at offset `pos` and length // `n`) as another string_view. This function throws `std::out_of_bounds` if - // `pos > size'. + // `pos > size`. string_view substr(size_type pos, size_type n = npos) const { if (ABSL_PREDICT_FALSE(pos > length_)) base_internal::ThrowStdOutOfRange("absl::string_view::substr"); @@ -354,7 +365,7 @@ class string_view { // string_view::compare() // // Performs a lexicographical comparison between the `string_view` and - // another `absl::string_view), returning -1 if `this` is less than, 0 if + // another `absl::string_view`, returning -1 if `this` is less than, 0 if // `this` is equal to, and 1 if `this` is greater than the passed std::string // view. Note that in the case of data equality, a further comparison is made // on the respective sizes of the two `string_view`s to determine which is @@ -482,23 +493,7 @@ class string_view { private: static constexpr size_type kMaxSize = - std::numeric_limits<difference_type>::max(); - - // check whether __builtin_strlen is provided by the compiler. - // GCC doesn't have __has_builtin() - // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66970), - // but has __builtin_strlen according to - // https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Other-Builtins.html. -#if ABSL_HAVE_BUILTIN(__builtin_strlen) || \ - (defined(__GNUC__) && !defined(__clang__)) - static constexpr size_type StrLenInternal(const char* str) { - return str ? __builtin_strlen(str) : 0; - } -#else - static constexpr size_type StrLenInternal(const char* str) { - return str ? strlen(str) : 0; - } -#endif + (std::numeric_limits<difference_type>::max)(); static constexpr size_type CheckLengthInternal(size_type len) { return ABSL_ASSERT(len <= kMaxSize), len; @@ -543,13 +538,13 @@ inline bool operator>=(string_view x, string_view y) noexcept { // IO Insertion Operator std::ostream& operator<<(std::ostream& o, string_view piece); -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_HAVE_STD_STRING_VIEW namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // ClippedSubstr() // @@ -570,7 +565,7 @@ inline string_view NullSafeStringView(const char* p) { return p ? string_view(p) : string_view(); } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_STRING_VIEW_H_ diff --git a/absl/strings/string_view_benchmark.cc b/absl/strings/string_view_benchmark.cc index fb46db18..f4420320 100644 --- a/absl/strings/string_view_benchmark.cc +++ b/absl/strings/string_view_benchmark.cc @@ -151,7 +151,7 @@ void BM_find_string_view_len_one(benchmark::State& state) { std::string haystack(state.range(0), '0'); absl::string_view s(haystack); for (auto _ : state) { - s.find("x"); // not present; length 1 + benchmark::DoNotOptimize(s.find("x")); // not present; length 1 } } BENCHMARK(BM_find_string_view_len_one)->Range(1, 1 << 20); @@ -160,7 +160,7 @@ void BM_find_string_view_len_two(benchmark::State& state) { std::string haystack(state.range(0), '0'); absl::string_view s(haystack); for (auto _ : state) { - s.find("xx"); // not present; length 2 + benchmark::DoNotOptimize(s.find("xx")); // not present; length 2 } } BENCHMARK(BM_find_string_view_len_two)->Range(1, 1 << 20); @@ -169,7 +169,7 @@ void BM_find_one_char(benchmark::State& state) { std::string haystack(state.range(0), '0'); absl::string_view s(haystack); for (auto _ : state) { - s.find('x'); // not present + benchmark::DoNotOptimize(s.find('x')); // not present } } BENCHMARK(BM_find_one_char)->Range(1, 1 << 20); @@ -178,7 +178,7 @@ void BM_rfind_one_char(benchmark::State& state) { std::string haystack(state.range(0), '0'); absl::string_view s(haystack); for (auto _ : state) { - s.rfind('x'); // not present + benchmark::DoNotOptimize(s.rfind('x')); // not present } } BENCHMARK(BM_rfind_one_char)->Range(1, 1 << 20); @@ -193,7 +193,7 @@ void BM_worst_case_find_first_of(benchmark::State& state, int haystack_len) { absl::string_view s(haystack); for (auto _ : state) { - s.find_first_of(needle); + benchmark::DoNotOptimize(s.find_first_of(needle)); } } diff --git a/absl/strings/string_view_test.cc b/absl/strings/string_view_test.cc index fffa7b99..ed34ed83 100644 --- a/absl/strings/string_view_test.cc +++ b/absl/strings/string_view_test.cc @@ -35,7 +35,8 @@ #define ABSL_EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ EXPECT_DEATH_IF_SUPPORTED(statement, ".*") #else -#define ABSL_EXPECT_DEATH_IF_SUPPORTED EXPECT_DEATH_IF_SUPPORTED +#define ABSL_EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + EXPECT_DEATH_IF_SUPPORTED(statement, regex) #endif namespace { @@ -283,7 +284,7 @@ TEST(StringViewTest, ComparisonOperatorsByCharacterPosition) { } #undef COMPARE -// Sadly, our users often confuse std::string::npos with absl::string_view::npos; +// Sadly, our users often confuse string::npos with absl::string_view::npos; // So much so that we test here that they are the same. They need to // both be unsigned, and both be the maximum-valued integer of their type. @@ -677,9 +678,9 @@ TEST(StringViewTest, STL2Substr) { EXPECT_EQ(a.substr(23, absl::string_view::npos), c); // throw exception #ifdef ABSL_HAVE_EXCEPTIONS - EXPECT_THROW(a.substr(99, 2), std::out_of_range); + EXPECT_THROW((void)a.substr(99, 2), std::out_of_range); #else - EXPECT_DEATH(a.substr(99, 2), "absl::string_view::substr"); + EXPECT_DEATH((void)a.substr(99, 2), "absl::string_view::substr"); #endif } @@ -811,15 +812,18 @@ TEST(StringViewTest, FrontBackSingleChar) { } // `std::string_view::string_view(const char*)` calls -// `std::char_traits<char>::length(const char*)` to get the std::string length. In +// `std::char_traits<char>::length(const char*)` to get the string length. In // libc++, it doesn't allow `nullptr` in the constexpr context, with the error // "read of dereferenced null pointer is not allowed in a constant expression". // At run time, the behavior of `std::char_traits::length()` on `nullptr` is -// undefined by the standard and usually results in crash with libc++. This -// conforms to the standard, but `absl::string_view` implements a different +// undefined by the standard and usually results in crash with libc++. +// In MSVC, creating a constexpr string_view from nullptr also triggers an +// "unevaluable pointer value" error. This compiler implementation conforms +// to the standard, but `absl::string_view` implements a different // behavior for historical reasons. We work around tests that construct // `string_view` from `nullptr` when using libc++. -#if !defined(ABSL_HAVE_STD_STRING_VIEW) || !defined(_LIBCPP_VERSION) +#if !defined(ABSL_HAVE_STD_STRING_VIEW) || \ + (!defined(_LIBCPP_VERSION) && !defined(_MSC_VER)) #define ABSL_HAVE_STRING_VIEW_FROM_NULLPTR 1 #endif // !defined(ABSL_HAVE_STD_STRING_VIEW) || !defined(_LIBCPP_VERSION) diff --git a/absl/strings/strip.h b/absl/strings/strip.h index ea64c334..059f57b7 100644 --- a/absl/strings/strip.h +++ b/absl/strings/strip.h @@ -17,7 +17,7 @@ // File: strip.h // ----------------------------------------------------------------------------- // -// This file contains various functions for stripping substrings from a std::string. +// This file contains various functions for stripping substrings from a string. #ifndef ABSL_STRINGS_STRIP_H_ #define ABSL_STRINGS_STRIP_H_ @@ -30,11 +30,11 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { // ConsumePrefix() // -// Strips the `expected` prefix from the start of the given std::string, returning +// Strips the `expected` prefix from the start of the given string, returning // `true` if the strip operation succeeded or false otherwise. // // Example: @@ -49,7 +49,7 @@ inline bool ConsumePrefix(absl::string_view* str, absl::string_view expected) { } // ConsumeSuffix() // -// Strips the `expected` suffix from the end of the given std::string, returning +// Strips the `expected` suffix from the end of the given string, returning // `true` if the strip operation succeeded or false otherwise. // // Example: @@ -65,9 +65,9 @@ inline bool ConsumeSuffix(absl::string_view* str, absl::string_view expected) { // StripPrefix() // -// Returns a view into the input std::string 'str' with the given 'prefix' removed, -// but leaving the original std::string intact. If the prefix does not match at the -// start of the std::string, returns the original std::string instead. +// Returns a view into the input string 'str' with the given 'prefix' removed, +// but leaving the original string intact. If the prefix does not match at the +// start of the string, returns the original string instead. ABSL_MUST_USE_RESULT inline absl::string_view StripPrefix( absl::string_view str, absl::string_view prefix) { if (absl::StartsWith(str, prefix)) str.remove_prefix(prefix.size()); @@ -76,16 +76,16 @@ ABSL_MUST_USE_RESULT inline absl::string_view StripPrefix( // StripSuffix() // -// Returns a view into the input std::string 'str' with the given 'suffix' removed, -// but leaving the original std::string intact. If the suffix does not match at the -// end of the std::string, returns the original std::string instead. +// Returns a view into the input string 'str' with the given 'suffix' removed, +// but leaving the original string intact. If the suffix does not match at the +// end of the string, returns the original string instead. ABSL_MUST_USE_RESULT inline absl::string_view StripSuffix( absl::string_view str, absl::string_view suffix) { if (absl::EndsWith(str, suffix)) str.remove_suffix(suffix.size()); return str; } -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_STRIP_H_ diff --git a/absl/strings/strip_test.cc b/absl/strings/strip_test.cc index 205c160c..40c4c607 100644 --- a/absl/strings/strip_test.cc +++ b/absl/strings/strip_test.cc @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file contains functions that remove a defined part from the std::string, -// i.e., strip the std::string. +// This file contains functions that remove a defined part from the string, +// i.e., strip the string. #include "absl/strings/strip.h" diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc index 849443b9..b70e70e8 100644 --- a/absl/strings/substitute.cc +++ b/absl/strings/substitute.cc @@ -23,7 +23,7 @@ #include "absl/strings/string_view.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace substitute_internal { void SubstituteAndAppendArray(std::string* output, absl::string_view format, @@ -168,5 +168,5 @@ Arg::Arg(Dec dec) { } } // namespace substitute_internal -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index def79be2..43d73ad7 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h @@ -17,46 +17,46 @@ // File: substitute.h // ----------------------------------------------------------------------------- // -// This package contains functions for efficiently performing std::string -// substitutions using a format std::string with positional notation: +// This package contains functions for efficiently performing string +// substitutions using a format string with positional notation: // `Substitute()` and `SubstituteAndAppend()`. // // Unlike printf-style format specifiers, `Substitute()` functions do not need // to specify the type of the substitution arguments. Supported arguments -// following the format std::string, such as strings, string_views, ints, +// following the format string, such as strings, string_views, ints, // floats, and bools, are automatically converted to strings during the // substitution process. (See below for a full list of supported types.) // // `Substitute()` does not allow you to specify *how* to format a value, beyond -// the default conversion to std::string. For example, you cannot format an integer +// the default conversion to string. For example, you cannot format an integer // in hex. // -// The format std::string uses positional identifiers indicated by a dollar sign ($) +// The format string uses positional identifiers indicated by a dollar sign ($) // and single digit positional ids to indicate which substitution arguments to -// use at that location within the format std::string. +// use at that location within the format string. // // Example 1: -// std::string s = Substitute("$1 purchased $0 $2. Thanks $1!", +// string s = Substitute("$1 purchased $0 $2. Thanks $1!", // 5, "Bob", "Apples"); // EXPECT_EQ("Bob purchased 5 Apples. Thanks Bob!", s); // // Example 2: -// std::string s = "Hi. "; +// string s = "Hi. "; // SubstituteAndAppend(&s, "My name is $0 and I am $1 years old.", "Bob", 5); // EXPECT_EQ("Hi. My name is Bob and I am 5 years old.", s); // // // Supported types: -// * absl::string_view, std::string, const char* (null is equivalent to "") +// * absl::string_view, string, const char* (null is equivalent to "") // * int32_t, int64_t, uint32_t, uint64 // * float, double // * bool (Printed as "true" or "false") -// * pointer types other than char* (Printed as "0x<lower case hex std::string>", +// * pointer types other than char* (Printed as "0x<lower case hex string>", // except that null is printed as "NULL") // -// If an invalid format std::string is provided, Substitute returns an empty std::string -// and SubstituteAndAppend does not change the provided output std::string. -// A format std::string is invalid if it: +// If an invalid format string is provided, Substitute returns an empty string +// and SubstituteAndAppend does not change the provided output string. +// A format string is invalid if it: // * ends in an unescaped $ character, // e.g. "Hello $", or // * calls for a position argument which is not provided, @@ -82,14 +82,14 @@ #include "absl/strings/strip.h" namespace absl { -inline namespace lts_2018_06_20 { +inline namespace lts_2018_12_18 { namespace substitute_internal { // Arg // // This class provides an argument type for `absl::Substitute()` and // `absl::SubstituteAndAppend()`. `Arg` handles implicit conversion of various -// types to a std::string. (`Arg` is very similar to the `AlphaNum` class in +// types to a string. (`Arg` is very similar to the `AlphaNum` class in // `StrCat()`.) // // This class has implicit constructors. @@ -198,8 +198,8 @@ constexpr int PlaceholderBitmask(const char* format) { // SubstituteAndAppend() // -// Substitutes variables into a given format std::string and appends to a given -// output std::string. See file comments above for usage. +// Substitutes variables into a given format string and appends to a given +// output string. See file comments above for usage. // // The declarations of `SubstituteAndAppend()` below consist of overloads // for passing 0 to 10 arguments, respectively. @@ -445,7 +445,7 @@ void SubstituteAndAppend( // Substitute() // -// Substitutes variables into a given format std::string. See file comments above +// Substitutes variables into a given format string. See file comments above // for usage. // // The declarations of `Substitute()` below consist of overloads for passing 0 @@ -457,7 +457,7 @@ void SubstituteAndAppend( // Example: // template <typename... Args> // void VarMsg(absl::string_view format, const Args&... args) { -// std::string s = absl::Substitute(format, args...); +// string s = absl::Substitute(format, args...); ABSL_MUST_USE_RESULT inline std::string Substitute(absl::string_view format) { std::string result; @@ -667,7 +667,7 @@ std::string Substitute( "format std::string doesn't contain all of $0 through $9"); #endif // ABSL_BAD_CALL_IF -} // inline namespace lts_2018_06_20 +} // inline namespace lts_2018_12_18 } // namespace absl #endif // ABSL_STRINGS_SUBSTITUTE_H_ |