From d179ec5f8bf700926b457ab454d141db1d711a1c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 15 May 2019 08:40:21 -0700 Subject: [PATCH 1/9] Simplify Grisu --- include/fmt/format-inl.h | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 29667beb66c2..372d887ded7c 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -647,29 +647,30 @@ template struct grisu_shortest_handler { return digits::more; } - // This implements Grisu3's round_weed. + // Decrement the generated number approaching value from above. + void round(uint64_t d, uint64_t divisor, uint64_t& remainder, + uint64_t error) { + while (remainder < d && error - remainder >= divisor && + (remainder + divisor < d || + d - remainder >= remainder + divisor - d)) { + --buf[size - 1]; + remainder += divisor; + } + } + + // Implements Grisu's round_weed. digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder, uint64_t error, int exp, bool integral) { buf[size++] = digit; if (remainder >= error) return digits::more; if (GRISU_VERSION != 3) { uint64_t d = integral ? diff : diff * data::POWERS_OF_10_64[-exp]; - while (remainder < d && error - remainder >= divisor && - (remainder + divisor < d || - d - remainder >= remainder + divisor - d)) { - --buf[size - 1]; - remainder += divisor; - } + round(d, divisor, remainder, error); return digits::done; } uint64_t unit = integral ? 1 : data::POWERS_OF_10_64[-exp]; uint64_t up = (diff - 1) * unit; // wp_Wup - while (remainder < up && error - remainder >= divisor && - (remainder + divisor < up || - up - remainder >= remainder + divisor - up)) { - --buf[size - 1]; - remainder += divisor; - } + round(up, divisor, remainder, error); uint64_t down = (diff + 1) * unit; // wp_Wdown if (remainder < down && error - remainder >= divisor && (remainder + divisor < down || From 25b72fc4cdb716cb04d1c8542296063f129b46bd Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 15 May 2019 08:43:54 -0700 Subject: [PATCH 2/9] Move to tests not to confuse users --- CMakeLists.txt | 3 +-- {include => test}/format | 0 2 files changed, 1 insertion(+), 2 deletions(-) rename {include => test}/format (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee0142c5ee48..b58edf049c9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,8 +151,7 @@ if (HAVE_OPEN) set(FMT_SOURCES ${FMT_SOURCES} src/posix.cc) endif () -add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} include/format README.rst - ChangeLog.rst) +add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) add_library(fmt::fmt ALIAS fmt) if (FMT_WERROR) diff --git a/include/format b/test/format similarity index 100% rename from include/format rename to test/format From 5ee08046315e2ffa5aef7a1638b1b44c754e5643 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 15 May 2019 10:02:40 -0700 Subject: [PATCH 3/9] Experiment with scan API --- test/CMakeLists.txt | 1 + test/scan-test.cc | 87 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 test/scan-test.cc diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7bbe63a5ca96..69c760850a82 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -105,6 +105,7 @@ add_fmt_test(prepare-test) add_fmt_test(printf-test) add_fmt_test(custom-formatter-test) add_fmt_test(ranges-test) +add_fmt_test(scan-test) # MSVC fails to compile GMock when C++17 is enabled. if (FMT_HAS_VARIANT AND NOT MSVC) diff --git a/test/scan-test.cc b/test/scan-test.cc new file mode 100644 index 000000000000..061f4abcef71 --- /dev/null +++ b/test/scan-test.cc @@ -0,0 +1,87 @@ +// Formatting library for C++ - scanning API proof of concept +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include +#include + +#include "fmt/format.h" +#include "gmock.h" + +namespace fmt { + +struct scan_arg { + int& value; + // TODO: more types +}; + +struct scan_args { + int size; + const scan_arg* data; + + template + scan_args(const std::array& store) + : size(N), data(store.data()) { + static_assert(N < std::numeric_limits::max(), "too many arguments"); + } +}; + +template +void parse_scan_format(string_view format_str, Handler& handler) { + const char* p = format_str.data(); + const char* end = p + format_str.size(); + while (p != end) { + char c = *p++; + if (c != '{' || p == end || *p++ != '}') + throw format_error("invalid format string"); + handler.on_arg(); + } +} + +void vscan(string_view input, string_view format_str, scan_args args) { + struct handler { + const char* begin; + const char* end; + scan_args args; + int next_arg_id; + + handler(string_view input, scan_args args) + : begin(input.data()), + end(begin + input.size()), + args(args), + next_arg_id(0) {} + + void on_arg() { + int value = 0; + while (begin != end) { + char c = *begin++; + if (c < '0' || c > '9') throw format_error("invalid input"); + value = value * 10 + (c - '0'); + } + if (next_arg_id >= args.size) + throw format_error("argument index out of range"); + args.data[0].value = value; + } + } h(input, args); + parse_scan_format(format_str, h); +} + +template +std::array make_scan_args(Args&... args) { + return {args...}; +} + +template +void scan(string_view input, string_view format_str, Args&... args) { + vscan(input, format_str, make_scan_args(args...)); +} +} // namespace fmt + +TEST(ScanTest, ReadInt) { + int n = 0; + fmt::scan("42", "{}", n); + EXPECT_EQ(n, 42); +} From a5ffa735db790e4b79efdcecd52c9962f9908879 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 15 May 2019 10:20:51 -0700 Subject: [PATCH 4/9] Fix gcc 4.4 build --- test/scan-test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/scan-test.cc b/test/scan-test.cc index 061f4abcef71..29387a066d9d 100644 --- a/test/scan-test.cc +++ b/test/scan-test.cc @@ -6,7 +6,7 @@ // For the license information refer to format.h. #include -#include +#include #include "fmt/format.h" #include "gmock.h" @@ -25,7 +25,7 @@ struct scan_args { template scan_args(const std::array& store) : size(N), data(store.data()) { - static_assert(N < std::numeric_limits::max(), "too many arguments"); + static_assert(N < INT_MAX, "too many arguments"); } }; From 67179dbc23a8ca3f2ffc9b6660485c59e4a8a1f3 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 15 May 2019 10:34:38 -0700 Subject: [PATCH 5/9] Remove deprecated format_decimal --- test/format-test.cc | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/test/format-test.cc b/test/format-test.cc index c6043586e455..14a7f87a0483 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1745,49 +1745,6 @@ TEST(FormatIntTest, FormatInt) { fmt::format_int(std::numeric_limits::max()).str()); } -#if defined(__GNUC__) || defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - -#if FMT_MSC_VER -# pragma warning(push) -# pragma warning(disable : 4996) // Using a deprecated function -#endif - -template std::string format_decimal(T value) { - char buffer[10]; - char* ptr = buffer; - // TODO: Replace with safer, non-deprecated overload - fmt::format_decimal(ptr, value); - return std::string(buffer, ptr); -} - -#if FMT_MSC_VER -# pragma warning(pop) -#endif - -#if defined(__GNUC__) || defined(__clang__) -# pragma GCC diagnostic pop -#endif - -TEST(FormatIntTest, FormatDec) { - EXPECT_EQ("-42", format_decimal(static_cast(-42))); - EXPECT_EQ("-42", format_decimal(static_cast(-42))); - std::ostringstream os; - os << std::numeric_limits::max(); - EXPECT_EQ(os.str(), - format_decimal(std::numeric_limits::max())); - EXPECT_EQ("1", format_decimal(1)); - EXPECT_EQ("-1", format_decimal(-1)); - EXPECT_EQ("42", format_decimal(42)); - EXPECT_EQ("-42", format_decimal(-42)); - EXPECT_EQ("42", format_decimal(42l)); - EXPECT_EQ("42", format_decimal(42ul)); - EXPECT_EQ("42", format_decimal(42ll)); - EXPECT_EQ("42", format_decimal(42ull)); -} - TEST(FormatTest, Print) { #if FMT_USE_FILE_DESCRIPTORS EXPECT_WRITE(stdout, fmt::print("Don't {}!", "panic"), "Don't panic!"); From e3e470bb69a4aa8968fbb30f09615509c2f8d616 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 15 May 2019 10:34:45 -0700 Subject: [PATCH 6/9] Remove deprecated format_decimal --- include/fmt/format.h | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index b082b456cb93..32317c77a595 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3036,34 +3036,6 @@ class format_int { std::string str() const { return std::string(str_, size()); } }; -// Formats a decimal integer value writing into buffer and returns -// a pointer to the end of the formatted string. This function doesn't -// write a terminating null character. -template -FMT_DEPRECATED inline void format_decimal(char*& buffer, T value) { - typedef typename internal::int_traits::main_type main_type; - main_type abs_value = static_cast(value); - if (internal::is_negative(value)) { - *buffer++ = '-'; - abs_value = 0 - abs_value; - } - if (abs_value < 100) { - if (abs_value < 10) { - *buffer++ = static_cast('0' + abs_value); - return; - } - unsigned index = static_cast(abs_value * 2); - *buffer++ = internal::data::DIGITS[index]; - *buffer++ = internal::data::DIGITS[index + 1]; - return; - } - int num_digits = internal::count_digits(abs_value); - internal::format_decimal( - internal::make_checked(buffer, internal::to_unsigned(num_digits)), - abs_value, num_digits); - buffer += num_digits; -} - // Formatter of objects of type T. template struct formatter Date: Wed, 15 May 2019 11:05:19 -0700 Subject: [PATCH 7/9] Fix gcc 4.4 build --- test/scan-test.cc | 93 +++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/test/scan-test.cc b/test/scan-test.cc index 29387a066d9d..e3efba7bbfdc 100644 --- a/test/scan-test.cc +++ b/test/scan-test.cc @@ -10,27 +10,18 @@ #include "fmt/format.h" #include "gmock.h" +#include "gtest-extra.h" -namespace fmt { +FMT_BEGIN_NAMESPACE +namespace internal { struct scan_arg { - int& value; + int* value; // TODO: more types }; -struct scan_args { - int size; - const scan_arg* data; - - template - scan_args(const std::array& store) - : size(N), data(store.data()) { - static_assert(N < INT_MAX, "too many arguments"); - } -}; - template -void parse_scan_format(string_view format_str, Handler& handler) { +void parse_scan_format(string_view format_str, Handler&& handler) { const char* p = format_str.data(); const char* end = p + format_str.size(); while (p != end) { @@ -40,48 +31,70 @@ void parse_scan_format(string_view format_str, Handler& handler) { handler.on_arg(); } } +} // namespace internal -void vscan(string_view input, string_view format_str, scan_args args) { - struct handler { - const char* begin; - const char* end; - scan_args args; - int next_arg_id; +struct scan_args { + int size; + const internal::scan_arg* data; - handler(string_view input, scan_args args) - : begin(input.data()), - end(begin + input.size()), - args(args), - next_arg_id(0) {} + template + scan_args(const std::array& store) + : size(N), data(store.data()) { + static_assert(N < INT_MAX, "too many arguments"); + } +}; + +namespace internal { +struct scan_handler { + const char* begin; + const char* end; + scan_args args; + int next_arg_id; + + scan_handler(string_view input, scan_args args) + : begin(input.data()), + end(begin + input.size()), + args(args), + next_arg_id(0) {} - void on_arg() { - int value = 0; - while (begin != end) { - char c = *begin++; - if (c < '0' || c > '9') throw format_error("invalid input"); - value = value * 10 + (c - '0'); - } - if (next_arg_id >= args.size) - throw format_error("argument index out of range"); - args.data[0].value = value; + void on_arg() { + int value = 0; + while (begin != end) { + char c = *begin++; + if (c < '0' || c > '9') throw format_error("invalid input"); + value = value * 10 + (c - '0'); } - } h(input, args); - parse_scan_format(format_str, h); + if (next_arg_id >= args.size) + throw format_error("argument index out of range"); + *args.data[0].value = value; + } +}; +} // namespace internal + +void vscan(string_view input, string_view format_str, scan_args args) { + internal::parse_scan_format(format_str, internal::scan_handler(input, args)); } template -std::array make_scan_args(Args&... args) { - return {args...}; +std::array make_scan_args(Args&... args) { + return std::array{&args...}; } template void scan(string_view input, string_view format_str, Args&... args) { vscan(input, format_str, make_scan_args(args...)); } -} // namespace fmt +FMT_END_NAMESPACE TEST(ScanTest, ReadInt) { int n = 0; fmt::scan("42", "{}", n); EXPECT_EQ(n, 42); } + +TEST(ScanTest, InvalidFormat) { + EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error, + "argument index out of range"); + EXPECT_THROW_MSG(fmt::scan("", "{"), fmt::format_error, + "invalid format string"); +} From 570453f271166983f110fc0783876bf12103d11f Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 16 May 2019 06:26:16 -0700 Subject: [PATCH 8/9] Add a vagrant config for testing gcc 4.4 --- support/Vagrantfile | 75 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 support/Vagrantfile diff --git a/support/Vagrantfile b/support/Vagrantfile new file mode 100644 index 000000000000..3762fa656f35 --- /dev/null +++ b/support/Vagrantfile @@ -0,0 +1,75 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : +# A vagrant config for testing against gcc-4.4. + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure("2") do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + # Every Vagrant development environment requires a box. You can search for + # boxes at https://vagrantcloud.com/search. + config.vm.box = "bento/ubuntu-10.04" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # NOTE: This will enable public access to the opened port + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine and only allow access + # via 127.0.0.1 to disable public access + # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + config.vm.provider "virtualbox" do |vb| + # # Display the VirtualBox GUI when booting the machine + # vb.gui = true + # + # Customize the amount of memory on the VM: + vb.memory = "4096" + end + + # View the documentation for the provider you are using for more + # information on available options. + + # Enable provisioning with a shell script. Additional provisioners such as + # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the + # documentation for more information about their specific syntax and use. + config.vm.provision "shell", inline: <<-SHELL + sed -i 's/us.archive/old-releases/' /etc/apt/sources.list + apt-get update + apt-get install -y g++ make wget + wget -q https://www.dropbox.com/s/ssvomsla6bkx2wl/cmake-3.14.4-Linux-x86_64.tar.gz + tar xzf cmake-3.14.4-Linux-x86_64.tar.gz + ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin + SHELL +end From 5e7bdf1b9793afb2b7d197267f44ffbf1d263641 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 16 May 2019 12:22:04 -0700 Subject: [PATCH 9/9] Clean up vagrant config --- support/Vagrantfile | 57 +-------------------------------------------- 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/support/Vagrantfile b/support/Vagrantfile index 3762fa656f35..d18aec3c2e0f 100644 --- a/support/Vagrantfile +++ b/support/Vagrantfile @@ -1,69 +1,14 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -# A vagrant config for testing against gcc-4.4. -# All Vagrant configuration is done below. The "2" in Vagrant.configure -# configures the configuration version (we support older styles for -# backwards compatibility). Please don't change it unless you know what -# you're doing. +# A vagrant config for testing against gcc-4.4. Vagrant.configure("2") do |config| - # The most common configuration options are documented and commented below. - # For a complete reference, please see the online documentation at - # https://docs.vagrantup.com. - - # Every Vagrant development environment requires a box. You can search for - # boxes at https://vagrantcloud.com/search. config.vm.box = "bento/ubuntu-10.04" - # Disable automatic box update checking. If you disable this, then - # boxes will only be checked for updates when the user runs - # `vagrant box outdated`. This is not recommended. - # config.vm.box_check_update = false - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine. In the example below, - # accessing "localhost:8080" will access port 80 on the guest machine. - # NOTE: This will enable public access to the opened port - # config.vm.network "forwarded_port", guest: 80, host: 8080 - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine and only allow access - # via 127.0.0.1 to disable public access - # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" - - # Create a private network, which allows host-only access to the machine - # using a specific IP. - # config.vm.network "private_network", ip: "192.168.33.10" - - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - # config.vm.network "public_network" - - # Share an additional folder to the guest VM. The first argument is - # the path on the host to the actual folder. The second argument is - # the path on the guest to mount the folder. And the optional third - # argument is a set of non-required options. - # config.vm.synced_folder "../data", "/vagrant_data" - - # Provider-specific configuration so you can fine-tune various - # backing providers for Vagrant. These expose provider-specific options. - # Example for VirtualBox: - # config.vm.provider "virtualbox" do |vb| - # # Display the VirtualBox GUI when booting the machine - # vb.gui = true - # - # Customize the amount of memory on the VM: vb.memory = "4096" end - # View the documentation for the provider you are using for more - # information on available options. - - # Enable provisioning with a shell script. Additional provisioners such as - # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the - # documentation for more information about their specific syntax and use. config.vm.provision "shell", inline: <<-SHELL sed -i 's/us.archive/old-releases/' /etc/apt/sources.list apt-get update