Merge pull request #548 from mpusz/new_fmt

feat!: New formatting grammar
This commit is contained in:
Mateusz Pusz
2024-01-23 23:28:59 +01:00
committed by GitHub
29 changed files with 1086 additions and 1115 deletions

View File

@@ -35,11 +35,12 @@ env:
jobs: jobs:
build: build:
name: "C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" name: "${{ matrix.formatting }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}"
runs-on: ${{ matrix.config.os }} runs-on: ${{ matrix.config.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
formatting: ["std::format", "fmtlib"]
std: [20, 23] std: [20, 23]
config: config:
# - { # - {
@@ -65,6 +66,7 @@ jobs:
cxx: "g++-12", cxx: "g++-12",
}, },
cxx_modules: "False", cxx_modules: "False",
std_format_support: "False",
conan-config: "", conan-config: "",
} }
- { - {
@@ -78,6 +80,7 @@ jobs:
cxx: "g++-13", cxx: "g++-13",
}, },
cxx_modules: "False", cxx_modules: "False",
std_format_support: "True",
conan-config: "", conan-config: "",
} }
- { - {
@@ -92,6 +95,7 @@ jobs:
}, },
lib: "libc++", lib: "libc++",
cxx_modules: "False", cxx_modules: "False",
std_format_support: "False",
conan-config: "", conan-config: "",
} }
- { - {
@@ -106,6 +110,7 @@ jobs:
}, },
lib: "libc++", lib: "libc++",
cxx_modules: "True", cxx_modules: "True",
std_format_support: "True",
conan-config: "", conan-config: "",
} }
- { - {
@@ -119,9 +124,13 @@ jobs:
cxx: "clang++", cxx: "clang++",
}, },
cxx_modules: "False", cxx_modules: "False",
std_format_support: "False",
conan-config: "", conan-config: "",
} }
build_type: ["Release", "Debug"] build_type: ["Release", "Debug"]
exclude:
- formatting: "std::format"
config: { std_format_support: "False" }
env: env:
CC: ${{ matrix.config.compiler.cc }} CC: ${{ matrix.config.compiler.cc }}
@@ -137,13 +146,14 @@ jobs:
cache-name: cache-conan-data cache-name: cache-conan-data
with: with:
path: ~/.conan2/p path: ~/.conan2/p
key: build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} key: build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }}
restore-keys: | restore-keys: |
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-
build-${{ matrix.config.os }}-${{ matrix.formatting }}-
build-${{ matrix.config.os }}- build-${{ matrix.config.os }}-
- uses: hendrikmuhs/ccache-action@v1.2 - uses: hendrikmuhs/ccache-action@v1.2
if: runner.os == 'Linux' if: runner.os == 'Linux'
@@ -196,12 +206,13 @@ jobs:
sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default
sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default
conan profile show -pr default conan profile show -pr default
- run: echo "use_fmtlib=$([ "${{ matrix.formatting }}" == "fmtlib" ] && echo "True" || echo "False")" >> $GITHUB_ENV
- name: Create Conan package - name: Create Conan package
shell: bash shell: bash
run: | run: |
conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \
-b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" \
-c user.build:all=True -o cxx_modules=${{ matrix.config.cxx_modules }} ${{ matrix.config.conan-config }} -c user.build:all=True -o cxx_modules=${{ matrix.config.cxx_modules }} -o use_fmtlib=${{ env.use_fmtlib }} ${{ matrix.config.conan-config }}
- name: Obtain package reference - name: Obtain package reference
id: get-package-ref id: get-package-ref
shell: bash shell: bash

View File

@@ -36,11 +36,12 @@ on:
jobs: jobs:
test_package: test_package:
name: "C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" name: "${{ matrix.formatting }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}"
runs-on: ${{ matrix.config.os }} runs-on: ${{ matrix.config.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
formatting: ["std::format", "fmtlib"]
std: [20, 23] std: [20, 23]
config: config:
# - { # - {
@@ -64,6 +65,7 @@ jobs:
cxx: "g++-12", cxx: "g++-12",
}, },
cxx_modules: "False", cxx_modules: "False",
std_format_support: "False",
} }
- { - {
name: "GCC-13", name: "GCC-13",
@@ -76,6 +78,7 @@ jobs:
cxx: "g++-13", cxx: "g++-13",
}, },
cxx_modules: "False", cxx_modules: "False",
std_format_support: "True",
} }
- { - {
name: "Clang-16", name: "Clang-16",
@@ -89,6 +92,7 @@ jobs:
}, },
lib: "libc++", lib: "libc++",
cxx_modules: "False", cxx_modules: "False",
std_format_support: "False",
} }
- { - {
name: "Clang-17", name: "Clang-17",
@@ -102,6 +106,7 @@ jobs:
}, },
lib: "libc++", lib: "libc++",
cxx_modules: "False", cxx_modules: "False",
std_format_support: "True",
} }
- { - {
name: "Apple Clang 15", name: "Apple Clang 15",
@@ -114,8 +119,12 @@ jobs:
cxx: "clang++", cxx: "clang++",
}, },
cxx_modules: "False", cxx_modules: "False",
std_format_support: "False",
} }
build_type: ["Release", "Debug"] build_type: ["Release", "Debug"]
exclude:
- formatting: "std::format"
config: { std_format_support: "False" }
env: env:
CC: ${{ matrix.config.compiler.cc }} CC: ${{ matrix.config.compiler.cc }}
@@ -136,13 +145,14 @@ jobs:
cache-name: cache-conan-data cache-name: cache-conan-data
with: with:
path: ~/.conan2/p path: ~/.conan2/p
key: build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} key: build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }}
restore-keys: | restore-keys: |
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-
build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}- build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-
build-${{ matrix.config.os }}-${{ matrix.formatting }}-
build-${{ matrix.config.os }}- build-${{ matrix.config.os }}-
- name: Install gcc-13 - name: Install gcc-13
if: matrix.config.compiler.type == 'GCC' && matrix.config.compiler.version == '13' if: matrix.config.compiler.type == 'GCC' && matrix.config.compiler.version == '13'
@@ -191,10 +201,12 @@ jobs:
sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default
sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default
conan profile show -pr default conan profile show -pr default
- run: echo "use_fmtlib=$([ "${{ matrix.formatting }}" == "fmtlib" ] && echo "True" || echo "False")" >> $GITHUB_ENV
- name: Install Conan dependencies - name: Install Conan dependencies
shell: bash shell: bash
run: | run: |
conan install . -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.build:all=False -o cxx_modules=${{ matrix.config.cxx_modules }} conan install . -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.build:all=False \
-o cxx_modules=${{ matrix.config.cxx_modules }} -o use_fmtlib=${{ env.use_fmtlib }}
mv CMakeUserPresets.json src mv CMakeUserPresets.json src
- name: Configure mp-units CMake - name: Configure mp-units CMake
if: matrix.config.compiler.type == 'VISUAL' || matrix.config.compiler.type == 'MSVC' if: matrix.config.compiler.type == 'VISUAL' || matrix.config.compiler.type == 'MSVC'

View File

@@ -105,7 +105,7 @@ jobs:
conan profile detect --force conan profile detect --force
conan remote add artifactory https://mpusz.jfrog.io/artifactory/api/conan/conan-oss conan remote add artifactory https://mpusz.jfrog.io/artifactory/api/conan/conan-oss
mkdir _lgtm_build_dir && cd _lgtm_build_dir mkdir _lgtm_build_dir && cd _lgtm_build_dir
conan build .. -s compiler.cppstd=20 -c user.build:all=True -b missing conan build .. -s compiler.cppstd=20 -c user.build:all=True -o use_fmtlib=True -b missing
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v2

View File

@@ -58,7 +58,7 @@ analysis and unit/quantity manipulation.
Here is a small example of possible operations: Here is a small example of possible operations:
```cpp ```cpp
#include <mp-units/systems/si/si.h> import mp_units;
using namespace mp_units; using namespace mp_units;
using namespace mp_units::si::unit_symbols; using namespace mp_units::si::unit_symbols;
@@ -66,7 +66,7 @@ using namespace mp_units::si::unit_symbols;
// simple numeric operations // simple numeric operations
static_assert(10 * km / 2 == 5 * km); static_assert(10 * km / 2 == 5 * km);
// unit conversions // conversions to common units
static_assert(1 * h == 3600 * s); static_assert(1 * h == 3600 * s);
static_assert(1 * km + 1 * m == 1001 * m); static_assert(1 * km + 1 * m == 1001 * m);
@@ -90,12 +90,9 @@ and dimensional analysis can be performed without sacrificing on runtime perform
accuracy. Please see the below example for a quick preview of basic library features: accuracy. Please see the below example for a quick preview of basic library features:
```cpp ```cpp
#include <mp-units/format.h> #include <iomanip>
#include <mp-units/ostream.h>
#include <mp-units/systems/international/international.h>
#include <mp-units/systems/isq/isq.h>
#include <mp-units/systems/si/si.h>
#include <iostream> #include <iostream>
import mp_units;
using namespace mp_units; using namespace mp_units;
@@ -118,13 +115,13 @@ int main()
constexpr quantity v6 = value_cast<m / s>(v4); constexpr quantity v6 = value_cast<m / s>(v4);
constexpr quantity v7 = value_cast<int>(v6); constexpr quantity v7 = value_cast<int>(v6);
std::cout << v1 << '\n'; // 110 km/h std::cout << v1 << '\n'; // 110 km/h
std::cout << v2 << '\n'; // 70 mi/h std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h
std::cout << std::format("{}", v3) << '\n'; // 110 km/h std::cout << std::format("{:*^10}\n", v3); // *110 km/h*
std::cout << std::format("{:*^14}", v4) << '\n'; // ***70 mi/h**** std::cout << std::format("{:%N in %U}\n", v4); // 70 in mi/h
std::cout << std::format("{:%Q in %q}", v5) << '\n'; // 30.5556 in m/s std::cout << std::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s
std::cout << std::format("{0:%Q} in {0:%q}", v6) << '\n'; // 31.2928 in m/s std::cout << std::format("{:{%N:.2f}%?{%U:n}}\n", v6); // 31.29 in m s⁻¹
std::cout << std::format("{:%Q}", v7) << '\n'; // 31 std::cout << std::format("{:%N}\n", v7); // 31
} }
``` ```

View File

@@ -28,7 +28,6 @@ from conan.errors import ConanInvalidConfiguration
from conan.tools.build import check_min_cppstd from conan.tools.build import check_min_cppstd
from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout
from conan.tools.files import copy, load, rmdir from conan.tools.files import copy, load, rmdir
from conan.tools.scm import Version
required_conan_version = ">=2.0.0" required_conan_version = ">=2.0.0"
@@ -60,7 +59,7 @@ class MPUnitsConan(ConanFile):
} }
default_options = { default_options = {
"cxx_modules": False, "cxx_modules": False,
"use_fmtlib": True, "use_fmtlib": False,
} }
tool_requires = "cmake/[>=3.28.1]" tool_requires = "cmake/[>=3.28.1]"
exports = ["LICENSE.md"] exports = ["LICENSE.md"]
@@ -88,6 +87,15 @@ class MPUnitsConan(ConanFile):
# , "msvc": "192" # , "msvc": "192"
} }
@property
def _std_format_minimum_compilers_version(self):
return {
"gcc": "13",
"clang": "17"
# , "apple-clang": "15"
# , "msvc": "192"
}
@property @property
def _build_all(self): def _build_all(self):
return bool(self.conf.get("user.build:all", default=False)) return bool(self.conf.get("user.build:all", default=False))
@@ -129,6 +137,13 @@ class MPUnitsConan(ConanFile):
raise ConanInvalidConfiguration( raise ConanInvalidConfiguration(
f"{self.ref} requires at least {compiler} {min_version} ({compiler.version} in use)" f"{self.ref} requires at least {compiler} {min_version} ({compiler.version} in use)"
) )
if not self.options.use_fmtlib:
min_version = self._std_format_minimum_compilers_version.get(str(compiler))
if min_version and loose_lt_semver(str(compiler.version), min_version):
raise ConanInvalidConfiguration(
f"`std::format` requires at least {compiler} {min_version} ({compiler.version} in use). "
"Use `-o use_fmtlib=True` instead."
)
def layout(self): def layout(self):
cmake_layout(self) cmake_layout(self)

View File

@@ -23,7 +23,7 @@ specific feature:
| Feature | gcc | clang | apple-clang | MSVC | | Feature | gcc | clang | apple-clang | MSVC |
|----------------------|:----:|:-----:|:-----------:|:----:| |----------------------|:----:|:-----:|:-----------:|:----:|
| **Minimum support** | 12 | 16 | 15 | None | | **Minimum support** | 12 | 16 | 15 | None |
| **`std::format`** | None | None | None | None | | **`std::format`** | 13 | 17 | None | None |
| **C++ modules** | None | 17 | None | None | | **C++ modules** | None | 17 | None | None |
| **C++23 extensions** | 14 | 18 | None | None | | **C++23 extensions** | 14 | 18 | None | None |
@@ -185,7 +185,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"}
[use_fmtlib](#use_fmtlib){ #use_fmtlib } [use_fmtlib](#use_fmtlib){ #use_fmtlib }
: [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `True`/`False` (Default: `True`) : [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `True`/`False` (Default: `False`)
Forces usage of [{fmt}](https://github.com/fmtlib/fmt) library instead of the C++20 Standard Forces usage of [{fmt}](https://github.com/fmtlib/fmt) library instead of the C++20 Standard
Library features. Library features.
@@ -232,7 +232,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"}
[`MP_UNITS_USE_FMTLIB`](#MP_UNITS_USE_FMTLIB){ #MP_UNITS_USE_FMTLIB } [`MP_UNITS_USE_FMTLIB`](#MP_UNITS_USE_FMTLIB){ #MP_UNITS_USE_FMTLIB }
: [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `ON`/`OFF` (Default: `ON`) : [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `ON`/`OFF` (Default: `OFF`)
Forces usage of [{fmt}](https://github.com/fmtlib/fmt) library instead of the C++20 Standard Forces usage of [{fmt}](https://github.com/fmtlib/fmt) library instead of the C++20 Standard
Library features. Library features.

View File

@@ -13,7 +13,7 @@ Here is a small example of operations possible on scalar quantities:
// simple numeric operations // simple numeric operations
static_assert(10 * km / 2 == 5 * km); static_assert(10 * km / 2 == 5 * km);
// unit conversions // conversions to common units
static_assert(1 * h == 3600 * s); static_assert(1 * h == 3600 * s);
static_assert(1 * km + 1 * m == 1001 * m); static_assert(1 * km + 1 * m == 1001 * m);
@@ -40,7 +40,7 @@ Here is a small example of operations possible on scalar quantities:
// simple numeric operations // simple numeric operations
static_assert(10 * km / 2 == 5 * km); static_assert(10 * km / 2 == 5 * km);
// unit conversions // conversions to common units
static_assert(1 * h == 3600 * s); static_assert(1 * h == 3600 * s);
static_assert(1 * km + 1 * m == 1001 * m); static_assert(1 * km + 1 * m == 1001 * m);
@@ -56,7 +56,7 @@ Here is a small example of operations possible on scalar quantities:
static_assert(1000 / (1 * s) == 1 * kHz); static_assert(1000 / (1 * s) == 1 * kHz);
``` ```
!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/81Ev7qhTd)" !!! example "[Try it on Compiler Explorer](https://godbolt.org/z/ox8a8dGTz)"
This library requires some C++20 features ([concepts and constraints](https://en.cppreference.com/w/cpp/language/constraints), This library requires some C++20 features ([concepts and constraints](https://en.cppreference.com/w/cpp/language/constraints),
@@ -69,6 +69,7 @@ performed without sacrificing accuracy. Please see the below example for a quick
=== "C++ modules" === "C++ modules"
```cpp ```cpp
#include <iomanip>
#include <iostream> #include <iostream>
import mp_units; import mp_units;
@@ -93,13 +94,13 @@ performed without sacrificing accuracy. Please see the below example for a quick
constexpr quantity v6 = value_cast<m / s>(v4); constexpr quantity v6 = value_cast<m / s>(v4);
constexpr quantity v7 = value_cast<int>(v6); constexpr quantity v7 = value_cast<int>(v6);
std::cout << v1 << '\n'; // 110 km/h std::cout << v1 << '\n'; // 110 km/h
std::cout << v2 << '\n'; // 70 mi/h std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h
std::cout << std::format("{}", v3) << '\n'; // 110 km/h std::cout << std::format("{:*^10}\n", v3); // *110 km/h*
std::cout << std::format("{:*^14}", v4) << '\n'; // ***70 mi/h**** std::cout << std::format("{:%N in %U}\n", v4); // 70 in mi/h
std::cout << std::format("{:%Q in %q}", v5) << '\n'; // 30.5556 in m/s std::cout << std::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s
std::cout << std::format("{0:%Q} in {0:%q}", v6) << '\n'; // 31.2928 in m/s std::cout << std::format("{:{%N:.2f}%?{%U:n}}\n", v6); // 31.29 in m s⁻¹
std::cout << std::format("{:%Q}", v7) << '\n'; // 31 std::cout << std::format("{:%N}\n", v7); // 31
} }
``` ```
@@ -111,6 +112,7 @@ performed without sacrificing accuracy. Please see the below example for a quick
#include <mp-units/systems/international/international.h> #include <mp-units/systems/international/international.h>
#include <mp-units/systems/isq/isq.h> #include <mp-units/systems/isq/isq.h>
#include <mp-units/systems/si/si.h> #include <mp-units/systems/si/si.h>
#include <iomanip>
#include <iostream> #include <iostream>
using namespace mp_units; using namespace mp_units;
@@ -134,13 +136,13 @@ performed without sacrificing accuracy. Please see the below example for a quick
constexpr quantity v6 = value_cast<m / s>(v4); constexpr quantity v6 = value_cast<m / s>(v4);
constexpr quantity v7 = value_cast<int>(v6); constexpr quantity v7 = value_cast<int>(v6);
std::cout << v1 << '\n'; // 110 km/h std::cout << v1 << '\n'; // 110 km/h
std::cout << v2 << '\n'; // 70 mi/h std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h
std::cout << std::format("{}", v3) << '\n'; // 110 km/h std::cout << std::format("{:*^10}\n", v3); // *110 km/h*
std::cout << std::format("{:*^14}", v4) << '\n'; // ***70 mi/h**** std::cout << std::format("{:%N in %U}\n", v4); // 70 in mi/h
std::cout << std::format("{:%Q in %q}", v5) << '\n'; // 30.5556 in m/s std::cout << std::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s
std::cout << std::format("{0:%Q} in {0:%q}", v6) << '\n'; // 31.2928 in m/s std::cout << std::format("{:{%N:.2f}%?{%U:n}}\n", v6); // 31.29 in m s⁻¹
std::cout << std::format("{:%Q}", v7) << '\n'; // 31 std::cout << std::format("{:%N}\n", v7); // 31
} }
``` ```

View File

@@ -31,7 +31,7 @@ The library source code is hosted on [GitHub](https://github.com/mpusz/mp-units)
| Feature | gcc | clang | apple-clang | MSVC | | Feature | gcc | clang | apple-clang | MSVC |
|----------------------|:----:|:-----:|:-----------:|:----:| |----------------------|:----:|:-----:|:-----------:|:----:|
| **Minimum support** | 12 | 16 | 15 | None | | **Minimum support** | 12 | 16 | 15 | None |
| **`std::format`** | None | None | None | None | | **`std::format`** | 13 | 17 | None | None |
| **C++ modules** | None | 17 | None | None | | **C++ modules** | None | 17 | None | None |
| **C++23 extensions** | 14 | 18 | None | None | | **C++23 extensions** | 14 | 18 | None | None |

View File

@@ -18,20 +18,20 @@ First, we either import the `mp_units` module or include the headers for:
- text formatting and stream output support - text formatting and stream output support
```cpp title="hello_units.cpp" linenums="1" ```cpp title="hello_units.cpp" linenums="1"
--8<-- "example/hello_units.cpp:28:39" --8<-- "example/hello_units.cpp:28:40"
``` ```
Also, to shorten the definitions, we "import" all the symbols from the `mp_units` namespace. Also, to shorten the definitions, we "import" all the symbols from the `mp_units` namespace.
```cpp title="hello_units.cpp" linenums="12" ```cpp title="hello_units.cpp" linenums="13"
--8<-- "example/hello_units.cpp:40:41" --8<-- "example/hello_units.cpp:41:42"
``` ```
Next, we define a simple function that calculates the average speed based on the provided Next, we define a simple function that calculates the average speed based on the provided
arguments of length and time: arguments of length and time:
```cpp title="hello_units.cpp" linenums="13" ```cpp title="hello_units.cpp" linenums="14"
--8<-- "example/hello_units.cpp:42:45" --8<-- "example/hello_units.cpp:43:46"
``` ```
The above function template takes any quantities implicitly convertible to `isq::length` The above function template takes any quantities implicitly convertible to `isq::length`
@@ -45,16 +45,16 @@ that its quantity type is implicitly convertible to `isq::speed`.
type is beneficial for users of such a function as it provides more information type is beneficial for users of such a function as it provides more information
of what to expect from a function than just using `auto`. of what to expect from a function than just using `auto`.
```cpp title="hello_units.cpp" linenums="17" ```cpp title="hello_units.cpp" linenums="18"
--8<-- "example/hello_units.cpp:47:50" --8<-- "example/hello_units.cpp:48:51"
``` ```
The above lines explicitly opt into using unit symbols from two systems of units. The above lines explicitly opt into using unit symbols from two systems of units.
As this introduces a lot of short identifiers into the current scope, it is not done As this introduces a lot of short identifiers into the current scope, it is not done
implicitly while including a header file. implicitly while including a header file.
```cpp title="hello_units.cpp" linenums="21" ```cpp title="hello_units.cpp" linenums="22"
--8<-- "example/hello_units.cpp:52:58" --8<-- "example/hello_units.cpp:53:59"
``` ```
- Lines `21` & `22` create a quantity of kind `isq::length / isq::time` with the numbers - Lines `21` & `22` create a quantity of kind `isq::length / isq::time` with the numbers
@@ -74,8 +74,8 @@ implicitly while including a header file.
- Line `27` does a [value-truncating conversion](../framework_basics/value_conversions.md#value-truncating-conversions) - Line `27` does a [value-truncating conversion](../framework_basics/value_conversions.md#value-truncating-conversions)
of changing the underlying representation type from `double` to `int`. of changing the underlying representation type from `double` to `int`.
```cpp title="hello_units.cpp" linenums="28" ```cpp title="hello_units.cpp" linenums="29"
--8<-- "example/hello_units.cpp:60" --8<-- "example/hello_units.cpp:61"
``` ```
The above presents [various ways to print a quantity](../framework_basics/text_output.md). The above presents [various ways to print a quantity](../framework_basics/text_output.md).

View File

@@ -72,4 +72,4 @@ add_example(total_energy)
add_example(unmanned_aerial_vehicle example_utils) add_example(unmanned_aerial_vehicle example_utils)
add_subdirectory(glide_computer_lib) add_subdirectory(glide_computer_lib)
add_subdirectory(kalman_filter) # add_subdirectory(kalman_filter)

View File

@@ -118,7 +118,7 @@ void calcs_comparison()
const auto L1A = 2.f * fm; const auto L1A = 2.f * fm;
const auto L2A = 3.f * fm; const auto L2A = 3.f * fm;
const auto LrA = L1A + L2A; const auto LrA = L1A + L2A;
std::cout << MP_UNITS_STD_FMT::format("{:%.30Q %q}\n + {:%.30Q %q}\n = {:%.30Q %q}\n\n", L1A, L2A, LrA); std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30} %U}\n + {:{%N:.30} %U}\n = {:{%N:.30} %U}\n\n", L1A, L2A, LrA);
std::cout << "The single unit method must convert large\n" std::cout << "The single unit method must convert large\n"
"or small values in other units to the base unit.\n" "or small values in other units to the base unit.\n"
@@ -127,17 +127,17 @@ void calcs_comparison()
const auto L1B = L1A.in(m); const auto L1B = L1A.in(m);
const auto L2B = L2A.in(m); const auto L2B = L2A.in(m);
const auto LrB = L1B + L2B; const auto LrB = L1B + L2B;
std::cout << MP_UNITS_STD_FMT::format("{:%.30eQ %q}\n + {:%.30eQ %q}\n = {:%.30eQ %q}\n\n", L1B, L2B, LrB); std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30e} %U}\n + {:{%N:.30e} %U}\n = {:{%N:.30e} %U}\n\n", L1B, L2B, LrB);
std::cout << "In multiplication and division:\n\n"; std::cout << "In multiplication and division:\n\n";
const quantity<isq::area[square(fm)], float> ArA = L1A * L2A; const quantity<isq::area[square(fm)], float> ArA = L1A * L2A;
std::cout << MP_UNITS_STD_FMT::format("{:%.30Q %q}\n * {:%.30Q %q}\n = {:%.30Q %q}\n\n", L1A, L2A, ArA); std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30} %U}\n * {:{%N:.30} %U}\n = {:{%N:.30} %U}\n\n", L1A, L2A, ArA);
std::cout << "similar problems arise\n\n"; std::cout << "similar problems arise\n\n";
const quantity<isq::area[m2], float> ArB = L1B * L2B; const quantity<isq::area[m2], float> ArB = L1B * L2B;
std::cout << MP_UNITS_STD_FMT::format("{:%.30eQ %q}\n * {:%.30eQ %q}\n = {:%.30eQ %q}\n\n", L1B, L2B, ArB); std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30e} %U}\n * {:{%N:.30e} %U}\n = {:{%N:.30e} %U}\n\n", L1B, L2B, ArB);
} }
} // namespace } // namespace

View File

@@ -58,7 +58,7 @@ int main()
std::cout << MP_UNITS_STD_FMT::format("therefore ratio lengthA / lengthB == {}\n\n", lengthA / lengthB); std::cout << MP_UNITS_STD_FMT::format("therefore ratio lengthA / lengthB == {}\n\n", lengthA / lengthB);
std::cout << MP_UNITS_STD_FMT::format("conversion factor from lengthA::unit of {:%q} to lengthB::unit of {:%q}:\n\n", std::cout << MP_UNITS_STD_FMT::format("conversion factor from lengthA::unit of {:%U} to lengthB::unit of {:%U}:\n\n",
lengthA, lengthB) lengthA, lengthB)
<< MP_UNITS_STD_FMT::format("lengthB.value( {} ) == lengthA.value( {} ) * conversion_factor( {} )\n", << MP_UNITS_STD_FMT::format("lengthB.value( {} ) == lengthA.value( {} ) * conversion_factor( {} )\n",
lengthB.numerical_value_ref_in(lengthB.unit), lengthB.numerical_value_ref_in(lengthB.unit),

View File

@@ -87,8 +87,8 @@ void print(const R& gliders)
std::cout << "- Polar:\n"; std::cout << "- Polar:\n";
for (const auto& p : g.polar) { for (const auto& p : g.polar) {
const auto ratio = glide_ratio(g.polar[0]).force_in(one); const auto ratio = glide_ratio(g.polar[0]).force_in(one);
std::cout << MP_UNITS_STD_FMT::format(" * {:%.4Q %q} @ {:%.1Q %q} -> {:%.1Q %q} ({:%.1Q %q})\n", p.climb, p.v, std::cout << MP_UNITS_STD_FMT::format(" * {:{%N:.4} %U} @ {:{%N:.1} %U} -> {:{%N:.1} %U} ({:{%N:.1} %U})\n",
ratio, p.climb, p.v, ratio,
// TODO is it possible to make ADL work below (we need another set of trig // TODO is it possible to make ADL work below (we need another set of trig
// functions for strong angle in a different namespace) // functions for strong angle in a different namespace)
si::asin(1 / ratio).force_in(si::degree)); si::asin(1 / ratio).force_in(si::degree));
@@ -106,8 +106,8 @@ void print(const R& conditions)
for (const auto& c : conditions) { for (const auto& c : conditions) {
std::cout << "- " << c.first << "\n"; std::cout << "- " << c.first << "\n";
const auto& w = c.second; const auto& w = c.second;
std::cout << " * Cloud base: " << MP_UNITS_STD_FMT::format("{:%.0Q %q}", w.cloud_base) << " AGL\n"; std::cout << " * Cloud base: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", w.cloud_base) << " AGL\n";
std::cout << " * Thermals strength: " << MP_UNITS_STD_FMT::format("{:%.1Q %q}", w.thermal_strength) << "\n"; std::cout << " * Thermals strength: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", w.thermal_strength) << "\n";
std::cout << "\n"; std::cout << "\n";
} }
} }
@@ -119,7 +119,7 @@ void print(const R& waypoints)
std::cout << "Waypoints:\n"; std::cout << "Waypoints:\n";
std::cout << "==========\n"; std::cout << "==========\n";
for (const auto& w : waypoints) for (const auto& w : waypoints)
std::cout << MP_UNITS_STD_FMT::format("- {}: {} {}, {:%.1Q %q}\n", w.name, w.pos.lat, w.pos.lon, w.alt); std::cout << MP_UNITS_STD_FMT::format("- {}: {} {}, {:{%N:.1} %U}\n", w.name, w.pos.lat, w.pos.lon, w.alt);
std::cout << "\n"; std::cout << "\n";
} }
@@ -130,12 +130,12 @@ void print(const task& t)
std::cout << "- Start: " << t.get_start().name << "\n"; std::cout << "- Start: " << t.get_start().name << "\n";
std::cout << "- Finish: " << t.get_finish().name << "\n"; std::cout << "- Finish: " << t.get_finish().name << "\n";
std::cout << "- Length: " << MP_UNITS_STD_FMT::format("{:%.1Q %q}", t.get_distance()) << "\n"; std::cout << "- Length: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", t.get_distance()) << "\n";
std::cout << "- Legs: " std::cout << "- Legs: "
<< "\n"; << "\n";
for (const auto& l : t.get_legs()) for (const auto& l : t.get_legs())
std::cout << MP_UNITS_STD_FMT::format(" * {} -> {} ({:%.1Q %q})\n", l.begin().name, l.end().name, std::cout << MP_UNITS_STD_FMT::format(" * {} -> {} ({:{%N:.1} %U})\n", l.begin().name, l.end().name,
l.get_distance()); l.get_distance());
std::cout << "\n"; std::cout << "\n";
} }
@@ -144,7 +144,7 @@ void print(const safety& s)
{ {
std::cout << "Safety:\n"; std::cout << "Safety:\n";
std::cout << "=======\n"; std::cout << "=======\n";
std::cout << "- Min AGL separation: " << MP_UNITS_STD_FMT::format("{:%.0Q %q}", s.min_agl_height) << "\n"; std::cout << "- Min AGL separation: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", s.min_agl_height) << "\n";
std::cout << "\n"; std::cout << "\n";
} }
@@ -153,8 +153,8 @@ void print(const aircraft_tow& tow)
std::cout << "Tow:\n"; std::cout << "Tow:\n";
std::cout << "====\n"; std::cout << "====\n";
std::cout << "- Type: aircraft\n"; std::cout << "- Type: aircraft\n";
std::cout << "- Height: " << MP_UNITS_STD_FMT::format("{:%.0Q %q}", tow.height_agl) << "\n"; std::cout << "- Height: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", tow.height_agl) << "\n";
std::cout << "- Performance: " << MP_UNITS_STD_FMT::format("{:%.1Q %q}", tow.performance) << "\n"; std::cout << "- Performance: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", tow.performance) << "\n";
std::cout << "\n"; std::cout << "\n";
} }

View File

@@ -82,7 +82,8 @@ void print(std::string_view phase_name, timestamp start_ts, const glide_computer
const glide_computer::flight_point& new_point) const glide_computer::flight_point& new_point)
{ {
std::cout << MP_UNITS_STD_FMT::format( std::cout << MP_UNITS_STD_FMT::format(
"| {:<12} | {:>9%.1Q %q} (Total: {:>9%.1Q %q}) | {:>8%.1Q %q} (Total: {:>8%.1Q %q}) | {:>7%.0Q %q} ({:>6%.0Q %q}) " "| {:<12} | {:>9{%N:.1} %U} (Total: {:>9{%N:.1} %U}) | {:>8{%N:.1} %U} (Total: {:>8{%N:.1} %U}) | {:>7{%N:.0} %U} "
"({:>6{%N:.0} %U}) "
"|\n", "|\n",
phase_name, value_cast<si::minute>(new_point.ts - point.ts), value_cast<si::minute>(new_point.ts - start_ts), phase_name, value_cast<si::minute>(new_point.ts - point.ts), value_cast<si::minute>(new_point.ts - start_ts),
new_point.dist - point.dist, new_point.dist, new_point.alt - point.alt, new_point.alt); new_point.dist - point.dist, new_point.dist, new_point.alt - point.alt, new_point.alt);

View File

@@ -26,6 +26,7 @@
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#include <mp-units/compat_macros.h> #include <mp-units/compat_macros.h>
#include <iomanip>
#include <iostream> #include <iostream>
#ifdef MP_UNITS_MODULES #ifdef MP_UNITS_MODULES
import mp_units; import mp_units;
@@ -57,11 +58,11 @@ int main()
constexpr quantity v6 = value_cast<m / s>(v4); constexpr quantity v6 = value_cast<m / s>(v4);
constexpr quantity v7 = value_cast<int>(v6); constexpr quantity v7 = value_cast<int>(v6);
std::cout << v1 << '\n'; // 110 km/h std::cout << v1 << '\n'; // 110 km/h
std::cout << v2 << '\n'; // 70 mi/h std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h
std::cout << MP_UNITS_STD_FMT::format("{}", v3) << '\n'; // 110 km/h std::cout << MP_UNITS_STD_FMT::format("{:*^10}\n", v3); // *110 km/h*
std::cout << MP_UNITS_STD_FMT::format("{:*^14}", v4) << '\n'; // ***70 mi/h**** std::cout << MP_UNITS_STD_FMT::format("{:%N in %U}\n", v4); // 70 in mi/h
std::cout << MP_UNITS_STD_FMT::format("{:%Q in %q}", v5) << '\n'; // 30.5556 in m/s std::cout << MP_UNITS_STD_FMT::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s
std::cout << MP_UNITS_STD_FMT::format("{0:%Q} in {0:%q}", v6) << '\n'; // 31.2928 in m/s std::cout << MP_UNITS_STD_FMT::format("{:{%N:.2f}%?{%U:n}}\n", v6); // 31.29 in m s⁻¹
std::cout << MP_UNITS_STD_FMT::format("{:%Q}", v7) << '\n'; // 31 std::cout << MP_UNITS_STD_FMT::format("{:%N}\n", v7); // 31
} }

View File

@@ -55,12 +55,14 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
} // namespace geographic } // namespace geographic
template<> template<typename Char>
struct MP_UNITS_STD_FMT::formatter<geographic::msl_altitude> : formatter<geographic::msl_altitude::quantity_type> { struct MP_UNITS_STD_FMT::formatter<geographic::msl_altitude, Char> :
formatter<geographic::msl_altitude::quantity_type, Char> {
template<typename FormatContext> template<typename FormatContext>
auto format(const geographic::msl_altitude& a, FormatContext& ctx) auto format(const geographic::msl_altitude& a, FormatContext& ctx) const -> decltype(ctx.out())
{ {
formatter<geographic::msl_altitude::quantity_type>::format(a - geographic::mean_sea_level, ctx); ctx.advance_to(
formatter<geographic::msl_altitude::quantity_type, Char>::format(a - geographic::mean_sea_level, ctx));
return MP_UNITS_STD_FMT::format_to(ctx.out(), " AMSL"); return MP_UNITS_STD_FMT::format_to(ctx.out(), " AMSL");
} }
}; };
@@ -137,29 +139,29 @@ class std::numeric_limits<geographic::longitude<T>> : public numeric_limits<T> {
static constexpr auto max() noexcept { return geographic::longitude<T>(180); } static constexpr auto max() noexcept { return geographic::longitude<T>(180); }
}; };
template<typename T> template<typename T, typename Char>
struct MP_UNITS_STD_FMT::formatter<geographic::latitude<T>> : struct MP_UNITS_STD_FMT::formatter<geographic::latitude<T>, Char> :
formatter<typename geographic::latitude<T>::quantity_type> { formatter<typename geographic::latitude<T>::quantity_type, Char> {
template<typename FormatContext> template<typename FormatContext>
auto format(geographic::latitude<T> lat, FormatContext& ctx) auto format(geographic::latitude<T> lat, FormatContext& ctx) const -> decltype(ctx.out())
{ {
const auto& q = lat.quantity_ref_from(geographic::equator); const auto& q = lat.quantity_ref_from(geographic::equator);
formatter<typename geographic::latitude<T>::quantity_type>::format(is_gteq_zero(q) ? q : -q, ctx); ctx.advance_to(
MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " N" : "S"); formatter<typename geographic::latitude<T>::quantity_type, Char>::format(is_gteq_zero(q) ? q : -q, ctx));
return ctx.out(); return MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " N" : "S");
} }
}; };
template<typename T> template<typename T, typename Char>
struct MP_UNITS_STD_FMT::formatter<geographic::longitude<T>> : struct MP_UNITS_STD_FMT::formatter<geographic::longitude<T>, Char> :
formatter<typename geographic::longitude<T>::quantity_type> { formatter<typename geographic::longitude<T>::quantity_type, Char> {
template<typename FormatContext> template<typename FormatContext>
auto format(geographic::longitude<T> lon, FormatContext& ctx) auto format(geographic::longitude<T> lon, FormatContext& ctx) const -> decltype(ctx.out())
{ {
const auto& q = lon.quantity_ref_from(geographic::prime_meridian); const auto& q = lon.quantity_ref_from(geographic::prime_meridian);
formatter<typename geographic::longitude<T>::quantity_type>::format(is_gteq_zero(q) ? q : -q, ctx); ctx.advance_to(
MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " E" : " W"); formatter<typename geographic::longitude<T>::quantity_type, Char>::format(is_gteq_zero(q) ? q : -q, ctx));
return ctx.out(); return MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " E" : " W");
} }
}; };

View File

@@ -63,11 +63,11 @@ template<typename T, auto Min, auto Max>
inline constexpr bool mp_units::treat_as_floating_point<ranged_representation<T, Min, Max>> = inline constexpr bool mp_units::treat_as_floating_point<ranged_representation<T, Min, Max>> =
mp_units::treat_as_floating_point<T>; mp_units::treat_as_floating_point<T>;
template<typename T, auto Min, auto Max> template<typename T, auto Min, auto Max, typename Char>
struct MP_UNITS_STD_FMT::formatter<ranged_representation<T, Min, Max>> : formatter<T> { struct MP_UNITS_STD_FMT::formatter<ranged_representation<T, Min, Max>, Char> : formatter<T, Char> {
template<typename FormatContext> template<typename FormatContext>
auto format(const ranged_representation<T, Min, Max>& v, FormatContext& ctx) auto format(const ranged_representation<T, Min, Max>& v, FormatContext& ctx) const -> decltype(ctx.out())
{ {
return formatter<T>::format(v.value(), ctx); return formatter<T, Char>::format(v.value(), ctx);
} }
}; };

View File

@@ -123,11 +123,11 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
} }
template<typename T, typename Validator> template<typename T, typename Validator, typename Char>
struct MP_UNITS_STD_FMT::formatter<validated_type<T, Validator>> : formatter<T> { struct MP_UNITS_STD_FMT::formatter<validated_type<T, Validator>, Char> : formatter<T, Char> {
template<typename FormatContext> template<typename FormatContext>
auto format(const validated_type<T, Validator>& v, FormatContext& ctx) auto format(const validated_type<T, Validator>& v, FormatContext& ctx) const -> decltype(ctx.out())
{ {
return formatter<T>::format(v.value(), ctx); return formatter<T, Char>::format(v.value(), ctx);
} }
}; };

View File

@@ -45,19 +45,19 @@ int main()
using namespace mp_units::si::unit_symbols; using namespace mp_units::si::unit_symbols;
std::cout << "The seven defining constants of the SI and the seven corresponding units they define:\n"; std::cout << "The seven defining constants of the SI and the seven corresponding units they define:\n";
std::cout << MP_UNITS_STD_FMT::format("- hyperfine transition frequency of Cs: {} = {:%.0Q %q}\n", std::cout << MP_UNITS_STD_FMT::format("- hyperfine transition frequency of Cs: {} = {:{%N:.0} %U}\n",
1. * si2019::hyperfine_structure_transition_frequency_of_cs, 1. * si2019::hyperfine_structure_transition_frequency_of_cs,
(1. * si2019::hyperfine_structure_transition_frequency_of_cs).in(Hz)); (1. * si2019::hyperfine_structure_transition_frequency_of_cs).in(Hz));
std::cout << MP_UNITS_STD_FMT::format("- speed of light in vacuum: {} = {:%.0Q %q}\n", std::cout << MP_UNITS_STD_FMT::format("- speed of light in vacuum: {} = {:{%N:.0} %U}\n",
1. * si2019::speed_of_light_in_vacuum, 1. * si2019::speed_of_light_in_vacuum,
(1. * si2019::speed_of_light_in_vacuum).in(m / s)); (1. * si2019::speed_of_light_in_vacuum).in(m / s));
std::cout << MP_UNITS_STD_FMT::format("- Planck constant: {} = {:%.8eQ %q}\n", std::cout << MP_UNITS_STD_FMT::format("- Planck constant: {} = {:{%N:.8e} %U}\n",
1. * si2019::planck_constant, (1. * si2019::planck_constant).in(J * s)); 1. * si2019::planck_constant, (1. * si2019::planck_constant).in(J * s));
std::cout << MP_UNITS_STD_FMT::format("- elementary charge: {} = {:%.9eQ %q}\n", std::cout << MP_UNITS_STD_FMT::format("- elementary charge: {} = {:{%N:.9e} %U}\n",
1. * si2019::elementary_charge, (1. * si2019::elementary_charge).in(C)); 1. * si2019::elementary_charge, (1. * si2019::elementary_charge).in(C));
std::cout << MP_UNITS_STD_FMT::format("- Boltzmann constant: {} = {:%.6eQ %q}\n", std::cout << MP_UNITS_STD_FMT::format("- Boltzmann constant: {} = {:{%N:.6e} %U}\n",
1. * si2019::boltzmann_constant, (1. * si2019::boltzmann_constant).in(J / K)); 1. * si2019::boltzmann_constant, (1. * si2019::boltzmann_constant).in(J / K));
std::cout << MP_UNITS_STD_FMT::format("- Avogadro constant: {} = {:%.8eQ %q}\n", std::cout << MP_UNITS_STD_FMT::format("- Avogadro constant: {} = {:{%N:.8e} %U}\n",
1. * si2019::avogadro_constant, (1. * si2019::avogadro_constant).in(one / mol)); 1. * si2019::avogadro_constant, (1. * si2019::avogadro_constant).in(one / mol));
std::cout << MP_UNITS_STD_FMT::format("- luminous efficacy: {} = {}\n", std::cout << MP_UNITS_STD_FMT::format("- luminous efficacy: {} = {}\n",
1. * si2019::luminous_efficacy, (1. * si2019::luminous_efficacy).in(lm / W)); 1. * si2019::luminous_efficacy, (1. * si2019::luminous_efficacy).in(lm / W));

View File

@@ -55,8 +55,8 @@ template<QuantityOf<isq::energy> T1, QuantityOf<isq::wavenumber> T2, QuantityOf<
QuantityOf<isq::thermodynamic_temperature> T4, QuantityOf<isq::wavelength> T5> QuantityOf<isq::thermodynamic_temperature> T4, QuantityOf<isq::wavelength> T5>
void print_line(const std::tuple<T1, T2, T3, T4, T5>& t) void print_line(const std::tuple<T1, T2, T3, T4, T5>& t)
{ {
MP_UNITS_STD_FMT::println("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |", std::get<0>(t), std::get<1>(t), std::cout << MP_UNITS_STD_FMT::format("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |\n", std::get<0>(t),
std::get<2>(t), std::get<3>(t), std::get<4>(t)); std::get<1>(t), std::get<2>(t), std::get<3>(t), std::get<4>(t));
} }
// prints quantities in semi-SI units // prints quantities in semi-SI units
@@ -65,9 +65,9 @@ template<QuantityOf<isq::energy> T1, QuantityOf<isq::wavenumber> T2, QuantityOf<
QuantityOf<isq::thermodynamic_temperature> T4, QuantityOf<isq::wavelength> T5> QuantityOf<isq::thermodynamic_temperature> T4, QuantityOf<isq::wavelength> T5>
void print_line_si(const std::tuple<T1, T2, T3, T4, T5>& t) void print_line_si(const std::tuple<T1, T2, T3, T4, T5>& t)
{ {
MP_UNITS_STD_FMT::println("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |", std::get<0>(t).in(eV), std::cout << MP_UNITS_STD_FMT::format("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |\n", std::get<0>(t).in(eV),
std::get<1>(t).in(one / cm), std::get<2>(t).in(THz), std::get<3>(t).in(K), std::get<1>(t).in(one / cm), std::get<2>(t).in(THz), std::get<3>(t).in(K),
std::get<4>(t).in(um)); std::get<4>(t).in(um));
} }
int main() int main()
@@ -92,16 +92,16 @@ int main()
const auto t5 = std::make_tuple(isq::energy(h * c / q5), isq::wavenumber(1 / q5), isq::frequency(c / q5), const auto t5 = std::make_tuple(isq::energy(h * c / q5), isq::wavenumber(1 / q5), isq::frequency(c / q5),
isq::thermodynamic_temperature(h * c / (q5 * kb)), q5); isq::thermodynamic_temperature(h * c / (q5 * kb)), q5);
MP_UNITS_STD_FMT::println("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |", "Energy", "Wavenumber", "Frequency", std::cout << MP_UNITS_STD_FMT::format("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |\n", "Energy", "Wavenumber",
"Temperature", "Wavelength"); "Frequency", "Temperature", "Wavelength");
MP_UNITS_STD_FMT::println("| {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} |", ""); std::cout << MP_UNITS_STD_FMT::format("| {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} |\n", "");
print_line(t1); print_line(t1);
print_line(t2); print_line(t2);
print_line(t3); print_line(t3);
print_line(t4); print_line(t4);
print_line(t5); print_line(t5);
MP_UNITS_STD_FMT::println("| {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} |", ""); std::cout << MP_UNITS_STD_FMT::format("| {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} |\n", "");
print_line_si(t1); print_line_si(t1);
print_line_si(t2); print_line_si(t2);
print_line_si(t3); print_line_si(t3);

View File

@@ -79,13 +79,13 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
return os << a - a.absolute_point_origin << " HAE(" << to_text(a.absolute_point_origin.egm) << ")"; return os << a - a.absolute_point_origin << " HAE(" << to_text(a.absolute_point_origin.egm) << ")";
} }
template<QuantityPoint QP> template<QuantityPoint QP, typename Char>
requires(is_hae(QP::absolute_point_origin)) requires(is_hae(QP::absolute_point_origin))
struct MP_UNITS_STD_FMT::formatter<QP> : formatter<typename QP::quantity_type> { struct MP_UNITS_STD_FMT::formatter<QP, Char> : formatter<typename QP::quantity_type, Char> {
template<typename FormatContext> template<typename FormatContext>
auto format(const QP& a, FormatContext& ctx) auto format(const QP& a, FormatContext& ctx) const -> decltype(ctx.out())
{ {
formatter<typename QP::quantity_type>::format(a - a.absolute_point_origin, ctx); formatter<typename QP::quantity_type, Char>::format(a - a.absolute_point_origin, ctx);
return MP_UNITS_STD_FMT::format_to(ctx.out(), " HAE({})", to_text(QP::absolute_point_origin.egm)); return MP_UNITS_STD_FMT::format_to(ctx.out(), " HAE({})", to_text(QP::absolute_point_origin.egm));
} }
}; };
@@ -123,12 +123,12 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
return os << a.quantity_from(height_above_launch) << " HAL"; return os << a.quantity_from(height_above_launch) << " HAL";
} }
template<> template<typename Char>
struct MP_UNITS_STD_FMT::formatter<hal_altitude> : formatter<hal_altitude::quantity_type> { struct MP_UNITS_STD_FMT::formatter<hal_altitude, Char> : formatter<hal_altitude::quantity_type, Char> {
template<typename FormatContext> template<typename FormatContext>
auto format(const hal_altitude& a, FormatContext& ctx) auto format(const hal_altitude& a, FormatContext& ctx) const -> decltype(ctx.out())
{ {
formatter<hal_altitude::quantity_type>::format(a.quantity_from(height_above_launch), ctx); formatter<hal_altitude::quantity_type, Char>::format(a.quantity_from(height_above_launch), ctx);
return MP_UNITS_STD_FMT::format_to(ctx.out(), " HAL"); return MP_UNITS_STD_FMT::format_to(ctx.out(), " HAL");
} }
}; };
@@ -170,6 +170,6 @@ int main()
}; };
waypoint wpt = {"EPPR", {54.24772_N, 18.6745_E}, mean_sea_level + 16. * ft}; waypoint wpt = {"EPPR", {54.24772_N, 18.6745_E}, mean_sea_level + 16. * ft};
std::cout << MP_UNITS_STD_FMT::format("{}: {} {}, {:%.2Q %q}, {:%.2Q %q}\n", wpt.name, wpt.pos.lat, wpt.pos.lon, std::cout << MP_UNITS_STD_FMT::format("{}: {} {}, {:{%N:.2} %U}, {:{%N:.2} %U}\n", wpt.name, wpt.pos.lat, wpt.pos.lon,
wpt.msl_alt, to_hae<earth_gravity_model::egm2008_1>(wpt.msl_alt, wpt.pos)); wpt.msl_alt, to_hae<earth_gravity_model::egm2008_1>(wpt.msl_alt, wpt.pos));
} }

View File

@@ -31,7 +31,7 @@ message(STATUS "${projectPrefix}BUILD_CXX_MODULES: ${${projectPrefix}BUILD_CXX_M
option(${projectPrefix}AS_SYSTEM_HEADERS "Exports library as system headers" OFF) option(${projectPrefix}AS_SYSTEM_HEADERS "Exports library as system headers" OFF)
message(STATUS "${projectPrefix}AS_SYSTEM_HEADERS: ${${projectPrefix}AS_SYSTEM_HEADERS}") message(STATUS "${projectPrefix}AS_SYSTEM_HEADERS: ${${projectPrefix}AS_SYSTEM_HEADERS}")
option(${projectPrefix}USE_FMTLIB "Enables usage of fmtlib instead of the 'std::format' facilities" ON) option(${projectPrefix}USE_FMTLIB "Enables usage of fmtlib instead of the 'std::format' facilities" OFF)
message(STATUS "${projectPrefix}USE_FMTLIB: ${${projectPrefix}USE_FMTLIB}") message(STATUS "${projectPrefix}USE_FMTLIB: ${${projectPrefix}USE_FMTLIB}")
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")

View File

@@ -22,6 +22,8 @@
#pragma once #pragma once
#include <version>
#if __clang__ #if __clang__
#define MP_UNITS_COMP_CLANG __clang_major__ #define MP_UNITS_COMP_CLANG __clang_major__
#elif __GNUC__ #elif __GNUC__
@@ -114,8 +116,8 @@ MP_UNITS_DIAGNOSTIC_POP
#define MP_UNITS_STD_FMT fmt #define MP_UNITS_STD_FMT fmt
#define MP_UNITS_FMT_LOCALE(loc) (loc).template get<std::locale>() #define MP_UNITS_FMT_LOCALE(loc) (loc).template get<std::locale>()
#define MP_UNITS_FMT_TO_ARG_ID(arg) static_cast<int>(arg) #define MP_UNITS_FMT_TO_ARG_ID(arg) (arg)
#define MP_UNITS_FMT_FROM_ARG_ID(arg) static_cast<size_t>(arg) #define MP_UNITS_FMT_FROM_ARG_ID(arg) (arg)
// This re-uses code from fmt; // This re-uses code from fmt;
#if FMT_EXCEPTIONS #if FMT_EXCEPTIONS
@@ -133,7 +135,7 @@ MP_UNITS_DIAGNOSTIC_POP
#else #else
#ifndef __cpp_lib_format #if !defined __cpp_lib_format && !defined MP_UNITS_COMP_CLANG
#error "std::formatting facility not supported" #error "std::formatting facility not supported"
#endif #endif
@@ -141,8 +143,8 @@ MP_UNITS_DIAGNOSTIC_POP
#define MP_UNITS_STD_FMT std #define MP_UNITS_STD_FMT std
#define MP_UNITS_FMT_LOCALE(loc) loc #define MP_UNITS_FMT_LOCALE(loc) loc
#define MP_UNITS_FMT_TO_ARG_ID(arg) arg #define MP_UNITS_FMT_TO_ARG_ID(arg) static_cast<std::size_t>(arg)
#define MP_UNITS_FMT_FROM_ARG_ID(arg) arg #define MP_UNITS_FMT_FROM_ARG_ID(arg) static_cast<int>(arg)
#define MP_UNITS_THROW(arg) throw arg #define MP_UNITS_THROW(arg) throw arg
#endif #endif

View File

@@ -35,15 +35,48 @@
#include <limits> #include <limits>
#include <string_view> #include <string_view>
// most of the below code is based on/copied from libfmt // most of the below code is based on/copied from fmtlib
namespace mp_units::detail { namespace mp_units::detail {
struct auto_id {}; enum class fmt_align { none, left, right, center, numeric };
enum class fmt_arg_id_kind {
none,
#if MP_UNITS_USE_FMTLIB
name,
#endif
index
};
enum class fmt_align { none, left, right, center }; template<typename Char>
enum class fmt_sign { none, minus, plus, space }; struct fmt_arg_ref {
enum class arg_id_kind { none, index, name }; fmt_arg_id_kind kind = fmt_arg_id_kind::none;
union value {
int index = 0;
#if MP_UNITS_USE_FMTLIB
std::basic_string_view<Char> name;
#endif
constexpr value() {}
constexpr value(int idx) : index(idx) {}
#if MP_UNITS_USE_FMTLIB
constexpr value(std::basic_string_view<Char> n) : name(n) {}
#endif
} val{};
fmt_arg_ref() = default;
constexpr explicit fmt_arg_ref(int index) : kind(fmt_arg_id_kind::index), val(index) {}
#if MP_UNITS_USE_FMTLIB
constexpr explicit fmt_arg_ref(std::basic_string_view<Char> name) : kind(fmt_arg_id_kind::name), val(name) {}
#endif
[[nodiscard]] constexpr fmt_arg_ref& operator=(int idx)
{
kind = fmt_arg_id_kind::index;
val.index = idx;
return *this;
}
};
template<typename Char> template<typename Char>
struct fill_t { struct fill_t {
@@ -58,7 +91,7 @@ public:
{ {
auto size = str.size(); auto size = str.size();
if (size > max_size) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill")); if (size > max_size) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill"));
for (size_t i = 0; i < size; ++i) data_[i] = str[i]; for (size_t i = 0; i < size && i < max_size; ++i) data_[i] = str[i];
size_ = static_cast<unsigned char>(size); size_ = static_cast<unsigned char>(size);
return *this; return *this;
} }
@@ -74,12 +107,6 @@ template<typename T>
inline constexpr bool is_integer = std::is_integral<T>::value && !std::is_same<T, bool>::value && inline constexpr bool is_integer = std::is_integral<T>::value && !std::is_same<T, bool>::value &&
!std::is_same<T, char>::value && !std::is_same<T, wchar_t>::value; !std::is_same<T, char>::value && !std::is_same<T, wchar_t>::value;
template<typename Char>
[[nodiscard]] constexpr bool is_ascii_letter(Char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
// Converts a character to ASCII. Returns a number > 127 on conversion failure. // Converts a character to ASCII. Returns a number > 127 on conversion failure.
template<std::integral Char> template<std::integral Char>
[[nodiscard]] constexpr Char to_ascii(Char value) [[nodiscard]] constexpr Char to_ascii(Char value)
@@ -89,232 +116,191 @@ template<std::integral Char>
template<typename Char> template<typename Char>
requires std::is_enum_v<Char> requires std::is_enum_v<Char>
[[nodiscard]] constexpr auto to_ascii(Char value) -> std::underlying_type_t<Char> [[nodiscard]] constexpr std::underlying_type_t<Char> to_ascii(Char value)
{ {
return value; return value;
} }
// Casts a nonnegative integer to unsigned.
template<typename Int>
[[nodiscard]] constexpr std::make_unsigned_t<Int> to_unsigned(Int value)
{
gsl_Expects(std::is_unsigned_v<Int> || value >= 0);
return static_cast<std::make_unsigned_t<Int>>(value);
}
struct width_checker { struct width_checker {
template<typename T> template<typename T>
[[nodiscard]] constexpr unsigned long long operator()(T value) const [[nodiscard]] constexpr unsigned long long operator()(T value) const
{ {
if constexpr (is_integer<T>) { if constexpr (is_integer<T>) {
if constexpr (std::numeric_limits<T>::is_signed) { if constexpr (std::numeric_limits<T>::is_signed)
if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width")); if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width"));
}
return static_cast<unsigned long long>(value); return static_cast<unsigned long long>(value);
} }
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("width is not integer")); MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("width is not integer"));
return 0; // should never happen return 0;
} }
}; };
struct precision_checker { template<class Handler, typename FormatArg>
template<typename T> [[nodiscard]] constexpr int get_dynamic_spec(FormatArg arg)
[[nodiscard]] constexpr unsigned long long operator()(T value) const
{
if constexpr (is_integer<T>) {
if constexpr (std::numeric_limits<T>::is_signed) {
if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative precision"));
}
return static_cast<unsigned long long>(value);
}
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("precision is not integer"));
return 0; // should never happen
}
};
// Format specifiers for built-in and string types.
template<typename Char>
struct basic_format_specs {
int width = 0;
int precision = -1;
char type = '\0';
fmt_align align : 4 = fmt_align::none;
fmt_sign sign : 3 = fmt_sign::none;
bool alt : 1 = false; // Alternate form ('#').
bool localized : 1 = false;
fill_t<Char> fill;
};
// Format specifiers with width and precision resolved at formatting rather
// than parsing time to allow re-using the same parsed specifiers with
// different sets of arguments (precompilation of format strings).
template<typename Char>
struct dynamic_format_specs : basic_format_specs<Char> {
int dynamic_width_index = -1;
int dynamic_precision_index = -1;
};
[[nodiscard]] constexpr int verify_dynamic_arg_index_in_range(size_t idx)
{ {
if (idx > static_cast<size_t>(std::numeric_limits<int>::max())) { const unsigned long long value = MP_UNITS_STD_FMT::visit_format_arg(Handler{}, arg);
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("Dynamic width or precision index too large.")); if (value > ::mp_units::detail::to_unsigned(std::numeric_limits<int>::max())) {
}
return static_cast<int>(idx);
}
template<typename CharT>
[[nodiscard]] constexpr int on_dynamic_arg(size_t arg_id, MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& context)
{
context.check_arg_id(MP_UNITS_FMT_TO_ARG_ID(arg_id));
return verify_dynamic_arg_index_in_range(arg_id);
}
template<typename CharT>
[[nodiscard]] constexpr int on_dynamic_arg(auto_id, MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& context)
{
return verify_dynamic_arg_index_in_range(MP_UNITS_FMT_FROM_ARG_ID(context.next_arg_id()));
}
template<class Handler, typename FormatContext>
[[nodiscard]] constexpr int get_dynamic_spec(int index, FormatContext& ctx)
{
const unsigned long long value =
MP_UNITS_STD_FMT::visit_format_arg(Handler{}, ctx.arg(MP_UNITS_FMT_TO_ARG_ID(static_cast<size_t>(index))));
if (value > static_cast<unsigned long long>(std::numeric_limits<int>::max())) {
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big")); MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
} }
return static_cast<int>(value); return static_cast<int>(value);
} }
template<typename Context, typename ID>
[[nodiscard]] constexpr auto get_arg(Context& ctx, ID id) -> decltype(ctx.arg(id))
{
auto arg = ctx.arg(MP_UNITS_FMT_TO_ARG_ID(id));
if (!arg) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("argument not found"));
return arg;
}
template<class Handler, typename Context>
constexpr void handle_dynamic_spec(int& value, fmt_arg_ref<typename Context::char_type> ref, Context& ctx)
{
switch (ref.kind) {
case fmt_arg_id_kind::none:
break;
case fmt_arg_id_kind::index:
value = ::mp_units::detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.index));
break;
#if MP_UNITS_USE_FMTLIB
case fmt_arg_id_kind::name:
value = ::mp_units::detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.name));
break;
#endif
}
}
// Parses the range [begin, end) as an unsigned integer. This function assumes // Parses the range [begin, end) as an unsigned integer. This function assumes
// that the range is non-empty and the first character is a digit. // that the range is non-empty and the first character is a digit.
template<std::input_iterator It, std::sentinel_for<It> S> template<typename Char>
[[nodiscard]] constexpr It parse_nonnegative_int(It begin, S end, size_t& value) [[nodiscard]] constexpr int parse_nonnegative_int(const Char*& begin, const Char* end, int error_value)
{ {
gsl_Expects(begin != end && '0' <= *begin && *begin <= '9'); gsl_Expects(begin != end && '0' <= *begin && *begin <= '9');
constexpr auto max_int = static_cast<unsigned>(std::numeric_limits<int>::max()); unsigned value = 0, prev = 0;
constexpr auto big_int = max_int / 10u; auto p = begin;
value = 0;
do { do {
if (value > big_int) { prev = value;
value = max_int + 1; value = value * 10 + unsigned(*p - '0');
break; ++p;
} } while (p != end && '0' <= *p && *p <= '9');
value = value * 10 + static_cast<unsigned int>(*begin - '0'); auto num_digits = p - begin;
++begin; begin = p;
} while (begin != end && '0' <= *begin && *begin <= '9'); if (num_digits <= std::numeric_limits<int>::digits10) return static_cast<int>(value);
// Check for overflow.
if (value > max_int) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("Number is too big")); const unsigned max = ::mp_units::detail::to_unsigned((std::numeric_limits<int>::max)());
return num_digits == std::numeric_limits<int>::digits10 + 1 && prev * 10ull + unsigned(p[-1] - '0') <= max
return begin; ? static_cast<int>(value)
: error_value;
} }
template<std::input_iterator It, std::sentinel_for<It> S> template<typename Char>
[[nodiscard]] constexpr It parse_nonnegative_int(It begin, S end, int& value) [[nodiscard]] constexpr bool is_name_start(Char c)
{ {
size_t val_unsigned = 0; return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_';
begin = parse_nonnegative_int(begin, end, val_unsigned);
// Never invalid because parse_nonnegative_integer throws an error for values that don't fit in signed integers
value = static_cast<int>(val_unsigned);
return begin;
} }
template<std::input_iterator It, std::sentinel_for<It> S, typename IDHandler> template<typename Char, typename Handler>
[[nodiscard]] constexpr It do_parse_arg_id(It begin, S end, IDHandler&& handler) [[nodiscard]] constexpr const Char* do_parse_arg_id(const Char* begin, const Char* end, Handler&& handler)
{ {
gsl_Expects(begin != end); Char c = *begin;
auto c = *begin;
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
size_t index = 0; int index = 0;
constexpr int max = (std::numeric_limits<int>::max)();
if (c != '0') if (c != '0')
begin = parse_nonnegative_int(begin, end, index); index = ::mp_units::detail::parse_nonnegative_int(begin, end, max);
else else
++begin; ++begin;
if (begin == end || (*begin != '}' && *begin != ':')) if (begin == end || (*begin != '}' && *begin != ':'))
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
else else
handler(index); handler.on_index(index);
return begin; return begin;
} }
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); if (c == '%') return begin; // mp-units extension
return begin; // should never happen if (!::mp_units::detail::is_name_start(c)) {
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
return begin;
}
auto it = begin;
do {
++it;
} while (it != end && (::mp_units::detail::is_name_start(*it) || ('0' <= *it && *it <= '9')));
#if MP_UNITS_USE_FMTLIB
handler.on_name({begin, ::mp_units::detail::to_unsigned(it - begin)});
#else
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("named arguments are not supported in the C++ standard facilities"));
#endif
return it;
} }
template<std::input_iterator It, std::sentinel_for<It> S, typename IDHandler> template<typename Char, typename Handler>
[[nodiscard]] constexpr It parse_arg_id(It begin, S end, IDHandler&& handler) [[nodiscard]] constexpr const Char* parse_arg_id(const Char* begin, const Char* end, Handler&& handler)
{
auto c = *begin;
if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler);
handler();
return begin;
}
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
[[nodiscard]] constexpr It parse_sign(It begin, S end, Handler&& handler)
{ {
gsl_Expects(begin != end); gsl_Expects(begin != end);
switch (to_ascii(*begin)) { Char c = *begin;
case '+': if (c != '}' && c != ':') return ::mp_units::detail::do_parse_arg_id(begin, end, handler);
handler.on_sign(fmt_sign::plus); handler.on_auto();
++begin;
break;
case '-':
handler.on_sign(fmt_sign::minus);
++begin;
break;
case ' ':
handler.on_sign(fmt_sign::space);
++begin;
break;
default:
break;
}
return begin; return begin;
} }
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler> template<typename Char>
[[nodiscard]] constexpr It parse_width(It begin, S end, Handler&& handler) struct dynamic_spec_id_handler {
{ MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx;
struct width_adapter { fmt_arg_ref<Char>& ref;
Handler& handler;
constexpr void operator()() { handler.on_dynamic_width(auto_id{}); }
constexpr void operator()(size_t id) { handler.on_dynamic_width(id); }
};
constexpr void on_auto()
{
int id = MP_UNITS_FMT_FROM_ARG_ID(ctx.next_arg_id());
ref = fmt_arg_ref<Char>(id);
#if MP_UNITS_USE_FMTLIB || __cpp_lib_format >= 202305L
ctx.check_dynamic_spec(id);
#endif
}
constexpr void on_index(int id)
{
ref = fmt_arg_ref<Char>(id);
ctx.check_arg_id(MP_UNITS_FMT_TO_ARG_ID(id));
#if MP_UNITS_USE_FMTLIB || __cpp_lib_format >= 202305L
ctx.check_dynamic_spec(id);
#endif
}
#if MP_UNITS_USE_FMTLIB
constexpr void on_name([[maybe_unused]] std::basic_string_view<Char> id)
{
ref = fmt_arg_ref<Char>(id);
ctx.check_arg_id(id);
}
#endif
};
template<typename Char>
[[nodiscard]] constexpr const Char* parse_dynamic_spec(const Char* begin, const Char* end, int& value,
fmt_arg_ref<Char>& ref,
MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx)
{
gsl_Expects(begin != end); gsl_Expects(begin != end);
if ('0' <= *begin && *begin <= '9') { if ('0' <= *begin && *begin <= '9') {
int width = 0; int val = ::mp_units::detail::parse_nonnegative_int(begin, end, -1);
begin = parse_nonnegative_int(begin, end, width); if (val != -1)
if (width != -1) value = val;
handler.on_width(width);
else else
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big")); MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
} else if (*begin == '{') { } else if (*begin == '{') {
++begin; ++begin;
if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); if (*begin == '%') return begin - 1; // mp-units extension
if (begin == end || *begin != '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); auto handler = dynamic_spec_id_handler<Char>{ctx, ref};
++begin; if (begin != end) begin = ::mp_units::detail::parse_arg_id(begin, end, handler);
} if (begin != end && *begin == '}') return ++begin;
return begin; MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
}
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
[[nodiscard]] constexpr It parse_precision(It begin, S end, Handler&& handler)
{
struct precision_adapter {
Handler& handler;
constexpr void operator()() { handler.on_dynamic_precision(auto_id{}); }
constexpr void operator()(size_t id) { handler.on_dynamic_precision(id); }
};
++begin;
auto c = begin != end ? *begin : std::iter_value_t<It>();
if ('0' <= c && c <= '9') {
auto precision = 0;
begin = parse_nonnegative_int(begin, end, precision);
if (precision != -1)
handler.on_precision(precision);
else
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
} else if (c == '{') {
++begin;
if (begin != end) begin = parse_arg_id(begin, end, precision_adapter{handler});
if (begin == end || *begin++ != '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
} else {
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("missing precision specifier"));
} }
return begin; return begin;
} }
@@ -334,13 +320,14 @@ constexpr int code_point_length(It begin)
} }
// Parses fill and alignment. // Parses fill and alignment.
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler> template<typename Char, typename Specs>
[[nodiscard]] constexpr It parse_align(It begin, S end, Handler&& handler) [[nodiscard]] constexpr const Char* parse_align(const Char* begin, const Char* end, Specs& specs,
fmt_align default_align = fmt_align::none)
{ {
gsl_Expects(begin != end); gsl_Expects(begin != end);
auto align = fmt_align::none; auto align = fmt_align::none;
auto p = begin + code_point_length(begin); auto p = begin + code_point_length(begin);
if (p >= end) p = begin; if (end - p <= 0) p = begin;
for (;;) { for (;;) {
switch (to_ascii(*p)) { switch (to_ascii(*p)) {
case '<': case '<':
@@ -352,120 +339,29 @@ template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
case '^': case '^':
align = fmt_align::center; align = fmt_align::center;
break; break;
default:
break;
} }
if (align != fmt_align::none) { if (align != fmt_align::none) {
if (p != begin) { if (p != begin) {
auto c = *begin; auto c = *begin;
if (c == '{') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill character '{'")); if (c == '}') return begin;
handler.on_fill(std::basic_string_view<std::iter_value_t<It>>(&*begin, static_cast<size_t>(p - begin))); if (c == '{') {
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill character '{'"));
return begin;
}
specs.fill = {begin, to_unsigned(p - begin)};
begin = p + 1; begin = p + 1;
} else } else {
++begin; ++begin;
handler.on_align(align); }
break; break;
} else if (p == begin) { } else if (p == begin) {
break; break;
} }
p = begin; p = begin;
} }
if (align == fmt_align::none) align = default_align; // mp-units extension
specs.align = align;
return begin; return begin;
} }
// Parses standard format specifiers and sends notifications about parsed
// components to handler.
template<std::input_iterator It, std::sentinel_for<It> S, typename SpecHandler>
[[nodiscard]] constexpr It parse_format_specs(It begin, S end, SpecHandler&& handler)
{
if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && *begin != 'L') {
handler.on_type(*begin++);
return begin;
}
if (begin == end) return begin;
begin = ::mp_units::detail::parse_align(begin, end, handler);
if (begin == end) return begin;
// Parse sign.
begin = ::mp_units::detail::parse_sign(begin, end, handler);
if (begin == end) return begin;
if (*begin == '#') {
handler.on_hash();
if (++begin == end) return begin;
}
// Parse zero flag.
if (*begin == '0') {
handler.on_zero();
if (++begin == end) return begin;
}
begin = ::mp_units::detail::parse_width(begin, end, handler);
if (begin == end) return begin;
// Parse precision.
if (*begin == '.') {
begin = ::mp_units::detail::parse_precision(begin, end, handler);
if (begin == end) return begin;
}
if (*begin == 'L') {
handler.on_localized();
++begin;
}
// Parse type.
if (begin != end && *begin != '}') handler.on_type(*begin++);
return begin;
}
// A format specifier handler that sets fields in basic_format_specs.
template<typename Char>
class specs_setter {
protected:
basic_format_specs<Char>& specs_;
public:
constexpr explicit specs_setter(basic_format_specs<Char>& specs) : specs_(specs) {}
constexpr void on_align(fmt_align align) { specs_.align = align; }
constexpr void on_fill(std::basic_string_view<Char> fill) { specs_.fill = fill; }
constexpr void on_sign(fmt_sign s) { specs_.sign = s; }
constexpr void on_hash() { specs_.alt = true; }
constexpr void on_localized() { specs_.localized = true; }
constexpr void on_zero() { specs_.fill[0] = Char('0'); }
constexpr void on_width(int width) { specs_.width = width; }
constexpr void on_precision(int precision) { specs_.precision = precision; }
constexpr void on_type(Char type) { specs_.type = static_cast<char>(type); }
};
// Format spec handler that saves references to arguments representing dynamic
// width and precision to be resolved at formatting time.
template<typename ParseContext>
class dynamic_specs_handler : public specs_setter<typename ParseContext::char_type> {
public:
using char_type = MP_UNITS_TYPENAME ParseContext::char_type;
constexpr dynamic_specs_handler(dynamic_format_specs<char_type>& specs, ParseContext& ctx) :
specs_setter<char_type>(specs), specs_(specs), context_(ctx)
{
}
template<typename T>
constexpr void on_dynamic_width(T t)
{
specs_.dynamic_width_index = on_dynamic_arg(t, context_);
}
template<typename T>
constexpr void on_dynamic_precision(T t)
{
specs_.dynamic_precision_index = on_dynamic_arg(t, context_);
}
private:
dynamic_format_specs<char_type>& specs_;
ParseContext& context_;
};
} // namespace mp_units::detail } // namespace mp_units::detail

View File

@@ -29,185 +29,68 @@
#include <mp-units/unit.h> #include <mp-units/unit.h>
#include <cstdint> #include <cstdint>
// Grammar
//
// quantity-format-spec ::= [fill-and-align] [width] [quantity-specs]
// quantity-specs ::= conversion-spec
// quantity-specs conversion-spec
// quantity-specs literal-char
// literal-char ::= any character other than '{' or '}'
// conversion-spec ::= '%' type
// type ::= [rep-modifier] 'Q'
// [unit-modifier] 'q'
// rep-modifier ::= [sign] [#] [precision] [L] [rep-type]
// rep-type ::= one of
// a A b B d e E f F g G o x X
// unit-modifier ::= [text-encoding] [unit-symbol-solidus] [unit-symbol-separator]
// [text-encoding] [unit-symbol-separator] [unit-symbol-solidus]
// [unit-symbol-solidus] [text-encoding] [unit-symbol-separator]
// [unit-symbol-solidus] [unit-symbol-separator] [text-encoding]
// [unit-symbol-separator] [text-encoding] [unit-symbol-solidus]
// [unit-symbol-separator] [unit-symbol-solidus] [text-encoding]
// text-encoding ::= one of
// U A
// unit-symbol-solidus ::= one of
// o a n
// unit-symbol-separator ::= one of
// s d
// TODO Should the below be allowed? Is it even possible to implement with `format()` being const?
// std::cout << std::format("{:%Q %q %Q %q}\n", 123s);
namespace mp_units::detail { namespace mp_units::detail {
// Holds specs about the whole object template<typename Char>
template<typename CharT> struct fill_align_width_format_specs {
struct quantity_global_format_specs { fill_t<Char> fill;
fill_t<CharT> fill; fmt_align align : 4 = fmt_align::none;
fmt_align align = fmt_align::none;
int width = 0; int width = 0;
int dynamic_width_index = -1; fmt_arg_ref<Char> width_ref;
}; };
// Holds specs about the representation (%[specs]Q) template<typename Char>
struct quantity_rep_format_specs { [[nodiscard]] constexpr const Char* at_most_one_of(const Char* begin, const Char* end, std::string_view modifiers)
fmt_sign sign = fmt_sign::none;
int precision = -1;
int dynamic_precision_index = -1;
char type = '\0';
bool alt = false;
bool localized = false;
};
// Holds specs about the unit (%[specs]q)
struct quantity_unit_format_specs : unit_symbol_formatting {};
template<typename CharT>
struct quantity_format_specs {
quantity_global_format_specs<CharT> global;
quantity_rep_format_specs rep;
quantity_unit_format_specs unit;
};
// Parse a `units-rep-modifier`
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
constexpr It parse_units_rep(It begin, S end, Handler&& handler, bool treat_as_floating_point)
{ {
// parse sign auto it = find_first_of(begin, end, modifiers.begin(), modifiers.end());
begin = parse_sign(begin, end, handler); if (it != end && find_first_of(it + 1, end, modifiers.begin(), modifiers.end()) != end)
if (begin == end) return begin; throw MP_UNITS_STD_FMT::format_error("only one of '" + std::string(modifiers) +
"' unit modifiers may be used in the format spec");
// parse # return it;
if (*begin == '#') {
handler.on_hash();
if (++begin == end) return begin;
}
// parse precision if a floating point
if (*begin == '.') {
if (treat_as_floating_point) {
begin = parse_precision(begin, end, handler);
} else
throw MP_UNITS_STD_FMT::format_error("precision not allowed for integral quantity representation");
if (begin == end) return begin;
}
// parse L to enable the locale-specific form
if (*begin == 'L') {
handler.on_localized();
++begin;
}
if (begin != end && *begin != '}' && *begin != '%') {
handler.on_type(*begin++);
}
return begin;
} }
// parse units-specs template<typename Char, typename Specs>
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler> [[nodiscard]] constexpr const Char* parse_fill_align_width(MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx,
constexpr It parse_units_format(It begin, S end, Handler&& handler) const Char* begin, const Char* end, Specs& specs,
fmt_align default_align = fmt_align::none)
{ {
auto ptr = begin; auto it = begin;
while (ptr != end) { if (it == end || *it == '}') return it;
auto c = *ptr;
if (c == '}') break;
if (c != '%') {
++ptr;
continue;
}
if (begin != ptr) handler.on_text(begin, ptr);
begin = ++ptr; // consume '%'
if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid format");
c = *ptr++;
constexpr auto units_types = std::string_view{"Qq"}; it = mp_units::detail::parse_align(it, end, specs, default_align);
const auto new_end = find_first_of(begin, end, units_types.begin(), units_types.end()); if (it == end) return it;
if (new_end == end) throw MP_UNITS_STD_FMT::format_error("invalid format");
if (*new_end == 'Q') { return mp_units::detail::parse_dynamic_spec(it, end, specs.width, specs.width_ref, ctx);
handler.on_quantity_value(begin, new_end); // Edit `on_quantity_value` to add rep modifiers
} else {
handler.on_quantity_unit(begin, new_end); // Edit `on_quantity_unit` to add an unit modifier
}
ptr = new_end + 1;
begin = ptr;
}
if (begin != ptr) handler.on_text(begin, ptr);
return ptr;
} }
// build the 'representation' as requested in the format string, applying only units-rep-modifiers template<typename Char, typename Handler>
template<typename CharT, typename Rep, typename OutputIt, typename Locale> [[nodiscard]] constexpr const Char* parse_subentity_replacement_field(const Char* begin, const Char* end,
[[nodiscard]] OutputIt format_units_quantity_value(OutputIt out, const Rep& val, Handler&& handler)
const quantity_rep_format_specs& rep_specs, const Locale& loc)
{ {
std::basic_string<CharT> buffer; if (end - begin++ < 4)
auto to_buffer = std::back_inserter(buffer); return MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` too short")), end;
if (*begin++ != '%')
MP_UNITS_STD_FMT::format_to(to_buffer, "{{:"); MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should start with '%'"));
switch (rep_specs.sign) { if (*begin == '}')
case fmt_sign::none: MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should have an identifier"));
break; auto it = begin;
case fmt_sign::plus: for (; it != end; ++it) {
MP_UNITS_STD_FMT::format_to(to_buffer, "+"); if (*it == '{' || *it == '%')
break; MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid `subentity-replacement-field` format"));
case fmt_sign::minus: if (*it == '}' || *it == ':') break;
MP_UNITS_STD_FMT::format_to(to_buffer, "-");
break;
case fmt_sign::space:
MP_UNITS_STD_FMT::format_to(to_buffer, " ");
break;
} }
if (it == end) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` too short"));
if (rep_specs.alt) { std::string_view id{begin, it};
MP_UNITS_STD_FMT::format_to(to_buffer, "#"); if (*it == ':') ++it;
} it = handler.on_replacement_field(id, it);
auto type = rep_specs.type; if (it == end || *it != '}')
if (auto precision = rep_specs.precision; precision >= 0) { MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should end with '}'"));
MP_UNITS_STD_FMT::format_to(to_buffer, ".{}{}", precision, type == '\0' ? 'f' : type); return ++it;
} else if constexpr (treat_as_floating_point<Rep>) {
MP_UNITS_STD_FMT::format_to(to_buffer, "{}", type == '\0' ? 'g' : type);
} else {
if (type != '\0') {
MP_UNITS_STD_FMT::format_to(to_buffer, "{}", type);
}
}
if (rep_specs.localized) {
MP_UNITS_STD_FMT::format_to(to_buffer, "L");
}
MP_UNITS_STD_FMT::format_to(to_buffer, "}}");
if (rep_specs.localized) {
return MP_UNITS_STD_FMT::vformat_to(out, MP_UNITS_FMT_LOCALE(loc), buffer, MP_UNITS_STD_FMT::make_format_args(val));
}
return MP_UNITS_STD_FMT::vformat_to(out, buffer, MP_UNITS_STD_FMT::make_format_args(val));
} }
// Creates a global format string template<typename OutputIt, typename Char>
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}" OutputIt format_global_buffer(OutputIt out, const fill_align_width_format_specs<Char>& specs)
template<typename CharT, typename OutputIt>
OutputIt format_global_buffer(OutputIt out, const quantity_global_format_specs<CharT>& specs)
{ {
MP_UNITS_STD_FMT::format_to(out, "{{:"); MP_UNITS_STD_FMT::format_to(out, "{{:");
if (specs.fill.size() != 1 || specs.fill[0] != ' ') { if (specs.fill.size() != 1 || specs.fill[0] != ' ') {
@@ -230,239 +113,346 @@ OutputIt format_global_buffer(OutputIt out, const quantity_global_format_specs<C
return MP_UNITS_STD_FMT::format_to(out, "}}"); return MP_UNITS_STD_FMT::format_to(out, "}}");
} }
template<auto Reference, typename Rep, typename Locale, typename CharT, typename OutputIt>
struct quantity_formatter {
OutputIt out;
Rep val;
const quantity_format_specs<CharT>& specs;
Locale loc;
explicit quantity_formatter(OutputIt o, const quantity<Reference, Rep>& q, const quantity_format_specs<CharT>& fspecs,
Locale lc) :
out(o), val(q.numerical_value_ref_in(q.unit)), specs(fspecs), loc(std::move(lc))
{
}
template<std::input_iterator It, std::sentinel_for<It> S>
void on_text(It begin, S end)
{
std::copy(begin, end, out);
}
template<std::input_iterator It, std::sentinel_for<It> S>
void on_quantity_value([[maybe_unused]] It, [[maybe_unused]] S)
{
out = format_units_quantity_value<CharT>(out, val, specs.rep, loc);
}
template<std::input_iterator It, std::sentinel_for<It> S>
void on_quantity_unit(It, S)
{
out = unit_symbol_to<CharT>(out, get_unit(Reference), specs.unit);
}
};
template<std::input_iterator It, std::sentinel_for<It> S>
[[nodiscard]] constexpr It at_most_one_of(It begin, S end, std::string_view modifiers)
{
auto it = find_first_of(begin, end, modifiers.begin(), modifiers.end());
if (it != end && find_first_of(it + 1, end, modifiers.begin(), modifiers.end()) != end)
throw MP_UNITS_STD_FMT::format_error("only one of '" + std::string(modifiers) +
"' unit modifiers may be used in the format spec");
return it;
}
} // namespace mp_units::detail } // namespace mp_units::detail
template<auto Reference, typename Rep, typename CharT> //
struct MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, CharT> { // Grammar
private: //
using quantity = mp_units::quantity<Reference, Rep>; // dimension-format-spec ::= [fill-and-align] [width] [dimension-spec]
using iterator = MP_UNITS_TYPENAME MP_UNITS_STD_FMT::basic_format_parse_context<CharT>::iterator; // dimension-spec ::= [text-encoding]
// text-encoding ::= 'U' | 'A'
//
bool quantity_value = false; // template<typename Char>
bool quantity_unit = false; // struct dimension_format_specs : fill_align_width_format_specs<Char>, dimension_symbol_formatting {};
mp_units::detail::quantity_format_specs<CharT> specs;
std::basic_string_view<CharT> format_str;
struct spec_handler {
formatter& f;
MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& context;
constexpr void on_fill(std::basic_string_view<CharT> fill) { f.specs.global.fill = fill; } //
constexpr void on_align(mp_units::detail::fmt_align align) { f.specs.global.align = align; } // Grammar
constexpr void on_width(int width) { f.specs.global.width = width; } //
constexpr void on_sign(mp_units::detail::fmt_sign sign) { f.specs.rep.sign = sign; } // unit-format-spec ::= [fill-and-align] [width] [unit-spec]
constexpr void on_hash() { f.specs.rep.alt = true; } // unit-spec ::= [text-encoding] [unit-symbol-solidus] [unit-symbol-separator] [L]
constexpr void on_precision(int precision) { f.specs.rep.precision = precision; } // [text-encoding] [unit-symbol-separator] [unit-symbol-solidus] [L]
constexpr void on_localized() { f.specs.rep.localized = true; } // [unit-symbol-solidus] [text-encoding] [unit-symbol-separator] [L]
// [unit-symbol-solidus] [unit-symbol-separator] [text-encoding] [L]
// [unit-symbol-separator] [text-encoding] [unit-symbol-solidus] [L]
// [unit-symbol-separator] [unit-symbol-solidus] [text-encoding] [L]
// unit-symbol-solidus ::= '1' | 'a' | 'n'1
// unit-symbol-separator ::= 's' | 'd'
//
template<mp_units::Unit U, typename Char>
class MP_UNITS_STD_FMT::formatter<U, Char> {
struct format_specs : mp_units::detail::fill_align_width_format_specs<Char>, mp_units::unit_symbol_formatting {};
constexpr void on_type(char type) std::basic_string_view<Char> fill_align_width_format_str_;
std::basic_string_view<Char> modifiers_format_str_;
format_specs specs_{};
struct format_checker {
using enum mp_units::text_encoding;
mp_units::text_encoding encoding = unicode;
constexpr void on_text_encoding(Char val) { encoding = (val == 'U') ? unicode : ascii; }
constexpr void on_unit_symbol_solidus(Char) const {}
constexpr void on_unit_symbol_separator(Char val) const
{ {
constexpr auto valid_rep_types = std::string_view{"aAbBdeEfFgGoxX"}; if (val == 'd' && encoding == ascii)
if (valid_rep_types.find(type) != std::string_view::npos) { throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for Unicode encoding");
f.specs.rep.type = type;
} else {
throw MP_UNITS_STD_FMT::format_error("invalid quantity type specifier");
}
}
template<typename T>
constexpr void on_dynamic_width(T t)
{
f.specs.global.dynamic_width_index = mp_units::detail::on_dynamic_arg(t, context);
}
template<typename T>
constexpr void on_dynamic_precision(T t)
{
f.specs.rep.dynamic_precision_index = mp_units::detail::on_dynamic_arg(t, context);
}
template<std::input_iterator It, std::sentinel_for<It> S>
constexpr void on_text(It, S)
{
}
template<std::input_iterator It, std::sentinel_for<It> S>
constexpr void on_quantity_value(It begin, S end)
{
if (begin != end) mp_units::detail::parse_units_rep(begin, end, *this, mp_units::treat_as_floating_point<Rep>);
f.quantity_value = true;
}
template<std::input_iterator It, std::sentinel_for<It> S>
constexpr void on_quantity_unit(It begin, S end)
{
if (begin == end) return;
constexpr auto valid_modifiers = std::string_view{"UAoansd"};
for (auto it = begin; it != end; ++it) {
if (valid_modifiers.find(*it) == std::string_view::npos)
throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified");
}
if (auto it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) {
if (*it == 'U')
f.specs.unit.encoding = mp_units::text_encoding::unicode;
else
f.specs.unit.encoding = mp_units::text_encoding::ascii;
}
if (auto it = mp_units::detail::at_most_one_of(begin, end, "oan"); it != end) {
if (*it == 'o')
f.specs.unit.solidus = mp_units::unit_symbol_solidus::one_denominator;
else if (*it == 'a')
f.specs.unit.solidus = mp_units::unit_symbol_solidus::always;
else
f.specs.unit.solidus = mp_units::unit_symbol_solidus::never;
}
if (auto it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) {
if (*it == 's')
f.specs.unit.separator = mp_units::unit_symbol_separator::space;
else {
if (f.specs.unit.encoding == mp_units::text_encoding::ascii)
throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for Unicode encoding");
f.specs.unit.separator = mp_units::unit_symbol_separator::half_high_dot;
}
}
f.quantity_unit = true;
} }
}; };
[[nodiscard]] constexpr std::pair<iterator, iterator> do_parse( struct unit_formatter {
MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& ctx) format_specs specs;
{
auto begin = ctx.begin();
auto end = ctx.end();
if (begin == end || *begin == '}') return {begin, begin}; using enum mp_units::text_encoding;
using enum mp_units::unit_symbol_solidus;
using enum mp_units::unit_symbol_separator;
// handler to assign parsed data to formatter data members constexpr void on_text_encoding(Char val) { specs.encoding = (val == 'U') ? unicode : ascii; }
spec_handler handler{*this, ctx}; constexpr void on_unit_symbol_solidus(Char val)
{
// parse alignment switch (val) {
begin = mp_units::detail::parse_align(begin, end, handler); case '1':
if (begin == end) return {begin, begin}; specs.solidus = one_denominator;
break;
// parse width case 'a':
begin = mp_units::detail::parse_width(begin, end, handler); specs.solidus = always;
if (begin == end) return {begin, begin}; break;
case 'n':
// parse units-specific specification specs.solidus = never;
end = mp_units::detail::parse_units_format(begin, end, handler); break;
if (specs.global.align == mp_units::detail::fmt_align::none && (!quantity_unit || quantity_value))
// quantity values should behave like numbers (by default aligned to right)
specs.global.align = mp_units::detail::fmt_align::right;
return {begin, end};
}
template<typename OutputIt, typename FormatContext>
OutputIt format_quantity_content(OutputIt out, const quantity& q, FormatContext& ctx)
{
auto begin = format_str.begin();
auto end = format_str.end();
if (begin == end || *begin == '}') {
// default format should print value followed by the unit separated with 1 space
out = mp_units::detail::format_units_quantity_value<CharT>(out, q.numerical_value_ref_in(q.unit), specs.rep,
ctx.locale());
if constexpr (mp_units::detail::has_unit_symbol(get_unit(Reference))) {
if constexpr (mp_units::space_before_unit_symbol<get_unit(Reference)>) *out++ = CharT(' ');
out = unit_symbol_to<CharT>(out, get_unit(Reference));
} }
} else {
// user provided format
mp_units::detail::quantity_formatter f(out, q, specs, ctx.locale());
mp_units::detail::parse_units_format(begin, end, f);
} }
return out; constexpr void on_unit_symbol_separator(Char val) { specs.separator = (val == 's') ? space : half_high_dot; }
};
template<typename Handler>
constexpr const Char* parse_unit_specs(const Char* begin, const Char* end, Handler&& handler) const
{
auto it = begin;
if (it == end || *it == '}') return begin;
constexpr auto valid_modifiers = std::string_view{"UA1ansd"};
for (; it != end && *it != '}'; ++it) {
if (valid_modifiers.find(*it) == std::string_view::npos)
throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified");
}
end = it;
if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) handler.on_text_encoding(*it);
if (it = mp_units::detail::at_most_one_of(begin, end, "1an"); it != end) handler.on_unit_symbol_solidus(*it);
if (it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) handler.on_unit_symbol_separator(*it);
return end;
} }
public: public:
[[nodiscard]] constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& ctx) constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin())
{ {
auto range = do_parse(ctx); const auto begin = ctx.begin();
if (range.first != range.second) auto end = ctx.end();
format_str = std::basic_string_view<CharT>(&*range.first, static_cast<size_t>(range.second - range.first));
return range.second; auto it = parse_fill_align_width(ctx, begin, end, specs_);
fill_align_width_format_str_ = {begin, it};
if (it == end) return it;
format_checker checker;
end = parse_unit_specs(it, end, checker);
modifiers_format_str_ = {it, end};
return end;
} }
template<typename FormatContext> template<typename FormatContext>
auto format(const quantity& q, FormatContext& ctx) auto format(const U& u, FormatContext& ctx) const -> decltype(ctx.out())
{ {
// process dynamic width and precision unit_formatter f{specs_};
if (specs.global.dynamic_width_index >= 0) mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(f.specs.width, f.specs.width_ref, ctx);
specs.global.width =
mp_units::detail::get_dynamic_spec<mp_units::detail::width_checker>(specs.global.dynamic_width_index, ctx);
if (specs.rep.dynamic_precision_index >= 0)
specs.rep.precision =
mp_units::detail::get_dynamic_spec<mp_units::detail::precision_checker>(specs.rep.dynamic_precision_index, ctx);
if (specs.global.width == 0) { parse_unit_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f);
if (f.specs.width == 0) {
// Avoid extra copying if width is not specified // Avoid extra copying if width is not specified
return format_quantity_content(ctx.out(), q, ctx); return mp_units::unit_symbol_to<Char>(ctx.out(), u, f.specs);
} else { } else {
// In `quantity_buffer` we will have the representation and the unit formatted according to their std::basic_string<Char> unit_buffer;
// specification, ignoring global specifiers mp_units::unit_symbol_to<Char>(std::back_inserter(unit_buffer), u, f.specs);
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "1.2_m"
std::basic_string<CharT> quantity_buffer;
// deal with quantity content std::basic_string<Char> global_format_buffer = "{:" + std::basic_string<Char>{fill_align_width_format_str_} + "}";
format_quantity_content(std::back_inserter(quantity_buffer), q, ctx);
// In `global_format_buffer` we will create a global format string
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}"
std::basic_string<CharT> global_format_buffer;
mp_units::detail::format_global_buffer<CharT>(std::back_inserter(global_format_buffer), specs.global);
// Format the `quantity buffer` using specs from `global_format_buffer`
// In the example, equivalent to MP_UNITS_STD_FMT::format("{:*^10}", "1.2_m")
return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer,
MP_UNITS_STD_FMT::make_format_args(unit_buffer));
}
}
};
//
// Grammar
//
// quantity-format-spec ::= [fill-and-align] [width] [quantity-specs]
// quantity-specs ::= conversion-spec
// quantity-specs conversion-spec
// quantity-specs literal-char
// literal-char ::= <any character other than '{', '}', or '%'>
// conversion-spec ::= placement-spec
// subentity-replacement-field
// placement-spec ::= '%' placement-type
// placement-type ::= 'N' | 'U' | 'D' | '?' | '%'
// subentity-replacement-field ::= '{' '%' subentity-id [format-specifier] '}'
// subentity-id ::= literal-char
// subentity-id literal-char
// format-specifier ::= ':' format-spec
// format-spec ::= <as specified by the formatter for the argument type; cannot start with '}'>
//
template<auto Reference, typename Rep, typename Char>
class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
static constexpr auto unit = get_unit(Reference);
static constexpr auto dimension = get_quantity_spec(Reference).dimension;
using quantity_t = mp_units::quantity<Reference, Rep>;
using unit_t = std::remove_const_t<decltype(unit)>;
using dimension_t = std::remove_const_t<decltype(dimension)>;
using format_specs = mp_units::detail::fill_align_width_format_specs<Char>;
std::basic_string_view<Char> modifiers_format_str_;
std::vector<size_t> format_str_lengths_;
format_specs specs_{};
struct format_checker {
MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx;
std::vector<size_t>& format_str_lengths;
constexpr void on_number(std::basic_string_view<Char>) const {}
constexpr void on_maybe_space() const {}
constexpr void on_unit(std::basic_string_view<Char>) const {}
constexpr void on_dimension(std::basic_string_view<Char>) const {}
constexpr void on_text(const Char*, const Char*) const {}
constexpr const Char* on_replacement_field(std::basic_string_view<Char> id, const Char* begin)
{
if (id == "N")
return on_replacement_field<Rep>(begin);
else if (id == "U")
return on_replacement_field<unit_t>(begin);
else if (id == "D") {
return begin;
// on_replacement_field<dimension_t>(begin);
} else
throw MP_UNITS_STD_FMT::format_error("unknown replacement field '" + std::string(id) + "'");
}
private:
template<typename T>
constexpr const Char* on_replacement_field(const Char* begin) const
{
MP_UNITS_STD_FMT::formatter<T> sf;
ctx.advance_to(begin);
auto ptr = sf.parse(ctx);
if (*ptr != '}') throw MP_UNITS_STD_FMT::format_error("unmatched '}' in format string");
format_str_lengths.push_back(mp_units::detail::to_unsigned(ptr - begin));
return ptr;
}
};
template<typename OutputIt>
struct quantity_formatter {
OutputIt out;
const quantity_t& q;
std::vector<size_t>::const_iterator format_str_lengths_it;
std::locale locale;
void on_number(std::basic_string_view<Char> format_str)
{
out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str,
MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit)));
}
void on_maybe_space()
{
if constexpr (mp_units::space_before_unit_symbol<unit>) *out++ = ' ';
}
void on_unit(std::basic_string_view<Char> format_str)
{
out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.unit));
}
void on_dimension(std::basic_string_view<Char>) {}
void on_text(const Char* begin, const Char* end) const { std::copy(begin, end, out); }
constexpr const Char* on_replacement_field(std::basic_string_view<Char> id, const Char* begin)
{
auto format_str = [&] { return "{:" + std::string(begin, *format_str_lengths_it + 1); };
if (id == "N")
on_number(format_str());
else if (id == "U")
on_unit(format_str());
else if (id == "D")
on_dimension(format_str());
else
throw MP_UNITS_STD_FMT::format_error("unknown replacement field '" + std::string(id) + "'");
return begin + *format_str_lengths_it++;
}
};
template<typename OutputIt, typename... Args>
quantity_formatter(OutputIt, Args...) -> quantity_formatter<OutputIt>;
template<typename Handler>
constexpr const Char* parse_quantity_specs(const Char* begin, const Char* end, Handler&& handler) const
{
if (begin == end || *begin == '}') return begin;
if (*begin != '%' && *begin != '{')
throw MP_UNITS_STD_FMT::format_error(
"`quantity-specs` should start with a `conversion-spec` ('%' or '{' characters expected)})");
auto ptr = begin;
while (ptr != end) {
auto c = *ptr;
if (c == '}') break;
if (c == '{') {
if (begin != ptr) handler.on_text(begin, ptr);
begin = ptr = mp_units::detail::parse_subentity_replacement_field(ptr, end, handler);
continue;
}
if (c != '%') {
++ptr;
continue;
}
if (begin != ptr) handler.on_text(begin, ptr);
++ptr; // consume '%'
if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid `placement-spec` format");
c = *ptr++;
switch (c) {
case 'N':
handler.on_number("{}");
break;
case 'U':
handler.on_unit("{}");
break;
case 'D':
handler.on_dimension("{}");
break;
case '?':
handler.on_maybe_space();
break;
case '%':
handler.on_text(ptr - 1, ptr);
break;
default:
throw MP_UNITS_STD_FMT::format_error(std::string("unknown `placement-spec` token '") + c + "'");
}
begin = ptr;
}
if (begin != ptr) handler.on_text(begin, ptr);
return ptr;
}
template<typename OutputIt, typename FormatContext>
OutputIt format_quantity(OutputIt out, const quantity_t& q, FormatContext& ctx) const
{
std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale());
if (modifiers_format_str_.empty()) {
// default format should print value followed by the unit separated with 1 space
out = MP_UNITS_STD_FMT::vformat_to(out, locale, "{}",
MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit)));
if constexpr (mp_units::space_before_unit_symbol<unit>) *out++ = ' ';
return MP_UNITS_STD_FMT::vformat_to(out, locale, "{}", MP_UNITS_STD_FMT::make_format_args(q.unit));
} else {
// user provided format
quantity_formatter f{out, q, format_str_lengths_.cbegin(), locale};
parse_quantity_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f);
return f.out;
}
}
public:
constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin())
{
const auto begin = ctx.begin();
auto end = ctx.end();
auto it = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right);
if (it == end) return it;
format_checker checker{ctx, format_str_lengths_};
end = parse_quantity_specs(it, end, checker);
modifiers_format_str_ = {it, end};
return end;
}
template<typename FormatContext>
auto format(const quantity_t& q, FormatContext& ctx) const -> decltype(ctx.out())
{
auto specs = specs_;
mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(specs.width, specs.width_ref, ctx);
if (specs.width == 0) {
// Avoid extra copying if width is not specified
return format_quantity(ctx.out(), q, ctx);
} else {
std::basic_string<Char> quantity_buffer;
format_quantity(std::back_inserter(quantity_buffer), q, ctx);
std::basic_string<Char> fill_align_width_format_str;
mp_units::detail::format_global_buffer(std::back_inserter(fill_align_width_format_str), specs);
return MP_UNITS_STD_FMT::vformat_to(ctx.out(), fill_align_width_format_str,
MP_UNITS_STD_FMT::make_format_args(quantity_buffer)); MP_UNITS_STD_FMT::make_format_args(quantity_buffer));
} }
} }

View File

@@ -31,6 +31,12 @@ namespace mp_units {
namespace detail { namespace detail {
template<typename CharT, class Traits, Unit U>
void to_stream(std::basic_ostream<CharT, Traits>& os, U u)
{
unit_symbol_to<CharT>(std::ostream_iterator<CharT>(os), u);
}
template<typename CharT, class Traits, auto R, typename Rep> template<typename CharT, class Traits, auto R, typename Rep>
void to_stream(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q) void to_stream(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
{ {
@@ -39,14 +45,29 @@ void to_stream(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
os << +q.numerical_value_ref_in(q.unit); os << +q.numerical_value_ref_in(q.unit);
else else
os << q.numerical_value_ref_in(q.unit); os << q.numerical_value_ref_in(q.unit);
if constexpr (has_unit_symbol(get_unit(R))) { if constexpr (space_before_unit_symbol<get_unit(R)>) os << " ";
if constexpr (space_before_unit_symbol<get_unit(R)>) os << " "; to_stream(os, get_unit(R));
unit_symbol_to<CharT>(std::ostream_iterator<CharT>(os), get_unit(R));
}
} }
} // namespace detail } // namespace detail
template<typename CharT, typename Traits, Unit U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, U u)
{
if (os.width()) {
// std::setw() applies to the whole uni output so it has to be first put into std::string
std::basic_ostringstream<CharT, Traits> oss;
oss.flags(os.flags());
oss.imbue(os.getloc());
oss.precision(os.precision());
detail::to_stream(oss, u);
return os << std::move(oss).str();
}
detail::to_stream(os, u);
return os;
}
template<typename CharT, typename Traits, auto R, typename Rep> template<typename CharT, typename Traits, auto R, typename Rep>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q) std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
requires requires { os << q.numerical_value_ref_in(q.unit); } requires requires { os << q.numerical_value_ref_in(q.unit); }

View File

@@ -653,6 +653,23 @@ template<Unit U1, Unit U2>
} }
/**
* @brief Puts a space ' ' sign before a unit symbol
*
* Quantities of some units (e.g. degree, arcminute, arcsecond) should not be printed with the
* space between a number and a unit. For those a partial specialization with the value `false` should
* be provided.
*/
template<Unit auto U>
inline constexpr bool space_before_unit_symbol = true;
template<>
inline constexpr bool space_before_unit_symbol<one> = false;
template<>
inline constexpr bool space_before_unit_symbol<percent> = false;
template<>
inline constexpr bool space_before_unit_symbol<per_mille> = false;
// get_unit_symbol // get_unit_symbol
enum class text_encoding : std::int8_t { enum class text_encoding : std::int8_t {
@@ -713,12 +730,6 @@ constexpr Out print_separator(Out out, unit_symbol_formatting fmt)
return out; return out;
} }
template<Unit U>
[[nodiscard]] consteval bool has_unit_symbol(U)
{
return requires { U::symbol; } || !std::derived_from<U, derived_unit<>>;
}
template<typename CharT, std::output_iterator<CharT> Out, Unit U> template<typename CharT, std::output_iterator<CharT> Out, Unit U>
requires requires { U::symbol; } requires requires { U::symbol; }
constexpr Out unit_symbol_impl(Out out, U, unit_symbol_formatting fmt, bool negative_power) constexpr Out unit_symbol_impl(Out out, U, unit_symbol_formatting fmt, bool negative_power)
@@ -741,12 +752,8 @@ constexpr Out unit_symbol_impl(Out out, const scaled_unit<M, U>& u, unit_symbol_
constexpr auto mag_txt = magnitude_text<M>(); constexpr auto mag_txt = magnitude_text<M>();
out = copy<CharT>(mag_txt, fmt.encoding, out); out = copy<CharT>(mag_txt, fmt.encoding, out);
if constexpr (has_unit_symbol(scaled_unit<M, U>::reference_unit)) { if constexpr (space_before_unit_symbol<scaled_unit<M, U>::reference_unit>) *out++ = ' ';
*out++ = ' '; return unit_symbol_impl<CharT>(out, u.reference_unit, fmt, negative_power);
return unit_symbol_impl<CharT>(out, u.reference_unit, fmt, negative_power);
} else {
return out;
}
} }
} }
@@ -828,21 +835,6 @@ constexpr Out unit_symbol_impl(Out out, const derived_unit<Expr...>&, unit_symbo
} // namespace detail } // namespace detail
/**
* @brief Puts a space ' ' sign before a unit symbol
*
* Quantities of some units (e.g. degree, arcminute, arcsecond) should not be printed with the
* space between a number and a unit. For those a partial specialization with the value `false` should
* be provided.
*/
template<Unit auto U>
inline constexpr bool space_before_unit_symbol = true;
template<>
inline constexpr bool space_before_unit_symbol<percent> = false;
template<>
inline constexpr bool space_before_unit_symbol<per_mille> = false;
template<typename CharT = char, std::output_iterator<CharT> Out, Unit U> template<typename CharT = char, std::output_iterator<CharT> Out, Unit U>
constexpr Out unit_symbol_to(Out out, U u, unit_symbol_formatting fmt = unit_symbol_formatting{}) constexpr Out unit_symbol_to(Out out, U u, unit_symbol_formatting fmt = unit_symbol_formatting{})
{ {

View File

@@ -33,6 +33,7 @@ import mp_units;
#include <mp-units/format.h> #include <mp-units/format.h>
#include <mp-units/ostream.h> #include <mp-units/ostream.h>
#include <mp-units/systems/cgs/cgs.h> #include <mp-units/systems/cgs/cgs.h>
#include <mp-units/systems/isq/electromagnetism.h>
#include <mp-units/systems/isq/mechanics.h> #include <mp-units/systems/isq/mechanics.h>
#include <mp-units/systems/isq/space_and_time.h> #include <mp-units/systems/isq/space_and_time.h>
#include <mp-units/systems/si/si.h> #include <mp-units/systems/si/si.h>
@@ -60,7 +61,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
SECTION("floating-point representation") SECTION("floating-point representation")
@@ -72,7 +76,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
} }
@@ -85,7 +92,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } SECTION("fmt with format {:%N%?%U} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); }
} }
SECTION("quantity with a derived unit") SECTION("quantity with a derived unit")
@@ -101,9 +108,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") SECTION("fmt with format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
} }
} }
@@ -116,9 +123,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") SECTION("fmt with format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
} }
} }
@@ -131,9 +138,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") SECTION("fmt with format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
} }
} }
} }
@@ -149,9 +156,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") SECTION("fmt with format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
} }
} }
@@ -164,9 +171,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") SECTION("fmt with format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
} }
} }
@@ -179,9 +186,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") SECTION("fmt with format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
} }
} }
@@ -194,9 +201,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") SECTION("fmt with format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
} }
} }
} }
@@ -213,7 +220,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "2 "); } SECTION("fmt with format {:%N%?%U} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == "2"); }
} }
SECTION("one with ratio.exp != 0") SECTION("one with ratio.exp != 0")
@@ -225,19 +232,27 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "2 km/m"); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == "2 km/m");
}
} }
SECTION("percents") SECTION("percents")
{ {
const auto q = value_cast<percent>(15. * isq::length[m] / (100. * isq::length[m])); constexpr auto q = value_cast<percent>(15. * isq::length[m] / (100. * isq::length[m]));
os << q; os << q;
static_assert(!space_before_unit_symbol<get_unit(q.reference)>);
SECTION("iostream") { CHECK(os.str() == "15%"); } SECTION("iostream") { CHECK(os.str() == "15%"); }
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "15 %"); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
SECTION("radians") SECTION("radians")
@@ -249,7 +264,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
} }
@@ -264,7 +282,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 °"); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
SECTION("arcminute") SECTION("arcminute")
@@ -276,7 +297,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 "); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
SECTION("arcsecond") SECTION("arcsecond")
@@ -288,7 +312,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 ″"); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
} }
@@ -303,7 +330,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
SECTION("signed negative") SECTION("signed negative")
@@ -315,7 +345,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
SECTION("unsigned") SECTION("unsigned")
@@ -327,20 +360,23 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); }
SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } SECTION("fmt with format {:%N%?%U} on a quantity")
{
CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str());
}
} }
} }
} }
TEST_CASE("format string with only %Q should print quantity value only", "[text][fmt]") TEST_CASE("quantity format string with only %N should print quantity value only", "[text][fmt]")
{ {
SECTION("integral representation") SECTION("integral representation")
{ {
SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%Q}", 123 * isq::speed[km / h]) == "123"); } SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%N}", 123 * isq::speed[km / h]) == "123"); }
SECTION("negative value") SECTION("negative value")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q}", 5 * isq::length[m] - 10 * isq::length[m]) == "-5"); CHECK(MP_UNITS_STD_FMT::format("{:%N}", 5 * isq::length[m] - 10 * isq::length[m]) == "-5");
} }
} }
@@ -348,185 +384,186 @@ TEST_CASE("format string with only %Q should print quantity value only", "[text]
{ {
SECTION("positive value") SECTION("positive value")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q}", 221. * isq::length[km] / (2 * isq::time[h])) == "110.5"); CHECK(MP_UNITS_STD_FMT::format("{:%N}", 221. * isq::length[km] / (2 * isq::time[h])) == "110.5");
} }
SECTION("negative value") SECTION("negative value")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q}", 3.14 * isq::length[m] - 10 * isq::length[m]) == "-6.86"); CHECK(MP_UNITS_STD_FMT::format("{:%N}", 3.14 * isq::length[m] - 10 * isq::length[m]) == "-6.859999999999999");
} }
SECTION("nan") SECTION("nan")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q}", std::numeric_limits<double>::quiet_NaN() * isq::length[m]) == "nan"); CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits<double>::quiet_NaN() * isq::length[m]) == "nan");
} }
SECTION("inf") SECTION("inf")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q}", std::numeric_limits<double>::infinity() * isq::length[m]) == "inf"); CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits<double>::infinity() * isq::length[m]) == "inf");
} }
SECTION("-inf") SECTION("-inf")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%Q}", -std::numeric_limits<double>::infinity() * isq::length[m]) == "-inf"); CHECK(MP_UNITS_STD_FMT::format("{:%N}", -std::numeric_limits<double>::infinity() * isq::length[m]) == "-inf");
} }
} }
} }
TEST_CASE("format string with only %q should print quantity unit symbol only", "[text][fmt]") TEST_CASE("quantity format string with only %U should print quantity unit symbol only", "[text][fmt]")
{ {
SECTION("Text encoding") CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::speed[km / h]) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::resistance[si::kilo<si::ohm>]) == "");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::time[us]) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::acceleration[m / s2]) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * percent) == "%");
}
TEST_CASE("Unit formatting should use proper text encoding")
{
SECTION("Unicode text output")
{ {
SECTION("Unicode text output") CHECK(MP_UNITS_STD_FMT::format("{:U}", km / h) == "km/h");
{ CHECK(MP_UNITS_STD_FMT::format("{:U}", si::kilo<si::ohm>) == "");
CHECK(MP_UNITS_STD_FMT::format("{:%Uq}", 123 * isq::speed[km / h]) == "km/h"); CHECK(MP_UNITS_STD_FMT::format("{:U}", us) == "µs");
// TODO enable this when resistance is defined CHECK(MP_UNITS_STD_FMT::format("{:U}", m / s2) == "m/s²");
// CHECK(MP_UNITS_STD_FMT::format("{:%Uq}", 123 * isq::resistance[kilo<ohm>]) == "kΩ");
CHECK(MP_UNITS_STD_FMT::format("{:%Uq}", 123 * isq::time[us]) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{:%Uq}", 123 * isq::acceleration[m / s2]) == "m/s²");
}
SECTION("Unicode text output is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::speed[km / h]) == "km/h");
// CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::resistance[kilo<ohm>]) == "kΩ");
CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::time[us]) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::acceleration[m / s2]) == "m/s²");
}
SECTION("ASCII text output")
{
CHECK(MP_UNITS_STD_FMT::format("{:%Aq}", 123 * isq::speed[km / h]) == "km/h");
// CHECK(MP_UNITS_STD_FMT::format("{:%Aq}", 123 * isq::resistance[kilo<ohm>]) == "kohm");
CHECK(MP_UNITS_STD_FMT::format("{:%Aq}", 123 * isq::time[us]) == "us");
CHECK(MP_UNITS_STD_FMT::format("{:%Aq}", 123 * isq::acceleration[m / s2]) == "m/s^2");
}
} }
SECTION("Solidus") SECTION("Unicode text output is used by default")
{ {
SECTION("Solidus for only one element in denominator") CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h");
{ CHECK(MP_UNITS_STD_FMT::format("{}", si::kilo<si::ohm>) == "");
CHECK(MP_UNITS_STD_FMT::format("{:%oq}", 123 * isq::speed[km / h]) == "km/h"); CHECK(MP_UNITS_STD_FMT::format("{}", us) == "µs");
CHECK(MP_UNITS_STD_FMT::format("{:%oq}", 123 * isq::acceleration[m / s2]) == "m/s²"); CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:%oq}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²");
}
SECTION("Solidus for only one element in denominator is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::speed[km / h]) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::acceleration[m / s2]) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²");
}
SECTION("Always use solidus")
{
CHECK(MP_UNITS_STD_FMT::format("{:%aq}", 123 * isq::speed[km / h]) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:%aq}", 123 * isq::acceleration[m / s2]) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:%aq}", 123 * isq::pressure[kg / m / s2]) == "kg/(m s²)");
}
SECTION("Never use solidus")
{
CHECK(MP_UNITS_STD_FMT::format("{:%nq}", 123 * isq::speed[km / h]) == "km h⁻¹");
CHECK(MP_UNITS_STD_FMT::format("{:%nq}", 123 * isq::acceleration[m / s2]) == "m s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:%nq}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²");
}
} }
SECTION("Separator") SECTION("ASCII text output")
{ {
SECTION("Space") CHECK(MP_UNITS_STD_FMT::format("{:A}", km / h) == "km/h");
{ CHECK(MP_UNITS_STD_FMT::format("{:A}", si::kilo<si::ohm>) == "kohm");
CHECK(MP_UNITS_STD_FMT::format("{:%sq}", 123 * isq::force[kg * m / s2]) == "kg m/s²"); CHECK(MP_UNITS_STD_FMT::format("{:A}", us) == "us");
CHECK(MP_UNITS_STD_FMT::format("{:%sq}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²"); CHECK(MP_UNITS_STD_FMT::format("{:A}", m / s2) == "m/s^2");
CHECK(MP_UNITS_STD_FMT::format("{:%asq}", 123 * isq::pressure[kg / m / s2]) == "kg/(m s²)"); }
} }
SECTION("Space is used by default") TEST_CASE("Unit formatting should print solidus according to specs")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::force[kg * m / s2]) == "kg m/s²"); SECTION("Solidus for only one element in denominator")
CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²"); {
CHECK(MP_UNITS_STD_FMT::format("{:%aq}", 123 * isq::pressure[kg / m / s2]) == "kg/(m s²)"); CHECK(MP_UNITS_STD_FMT::format("{:1}", km / h) == "km/h");
} CHECK(MP_UNITS_STD_FMT::format("{:1}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:1}", kg / m / s2) == "kg m⁻¹ s⁻²");
}
SECTION("Dot") SECTION("Solidus for only one element in denominator is used by default")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%dq}", 123 * isq::force[kg * m / s2]) == "kg⋅m/s²"); CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:%dq}", 123 * isq::pressure[kg / m / s2]) == "kg⋅m⁻¹⋅s⁻²"); CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:%adq}", 123 * isq::pressure[kg / m / s2]) == "kg/(m⋅s²)"); CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²");
} }
SECTION("Always use solidus")
{
CHECK(MP_UNITS_STD_FMT::format("{:a}", km / h) == "km/h");
CHECK(MP_UNITS_STD_FMT::format("{:a}", m / s2) == "m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Never use solidus")
{
CHECK(MP_UNITS_STD_FMT::format("{:n}", km / h) == "km h⁻¹");
CHECK(MP_UNITS_STD_FMT::format("{:n}", m / s2) == "m s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:n}", kg / m / s2) == "kg m⁻¹ s⁻²");
}
}
TEST_CASE("Unit formatting should user proper separator")
{
SECTION("Space")
{
CHECK(MP_UNITS_STD_FMT::format("{:s}", kg * m / s2) == "kg m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:s}", kg / m / s2) == "kg m⁻¹ s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:sa}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Space is used by default")
{
CHECK(MP_UNITS_STD_FMT::format("{}", kg * m / s2) == "kg m/s²");
CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)");
}
SECTION("Dot")
{
CHECK(MP_UNITS_STD_FMT::format("{:d}", kg * m / s2) == "kg⋅m/s²");
CHECK(MP_UNITS_STD_FMT::format("{:d}", kg / m / s2) == "kg⋅m⁻¹⋅s⁻²");
CHECK(MP_UNITS_STD_FMT::format("{:ad}", kg / m / s2) == "kg/(m⋅s²)");
} }
} }
TEST_CASE("unknown unit modifiers should throw", "[text][fmt][exception]") TEST_CASE("unknown unit modifiers should throw", "[text][fmt][exception]")
{ {
auto q = 1 * isq::length[m];
SECTION("only the invalid modifier") SECTION("only the invalid modifier")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%xq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:x}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified"));
} }
SECTION("invalid modifier in the front") SECTION("invalid modifier in the front")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%xUdaq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:xUda}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified"));
} }
SECTION("invalid modifier in the end") SECTION("invalid modifier in the end")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%Udaxq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udax}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified"));
} }
SECTION("invalid modifier in the middle") SECTION("invalid modifier in the middle")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%Udxaq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udxa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified"));
} }
} }
TEST_CASE("repeated unit modifiers should throw", "[text][fmt][exception]") TEST_CASE("repeated unit modifiers should throw", "[text][fmt][exception]")
{ {
auto q = 1 * isq::length[m];
SECTION("text encoding") SECTION("text encoding")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%UdaUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:UdaU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dUaUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUaU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dUUaq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUUa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec"));
} }
SECTION("solidus") SECTION("solidus")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%aUdaq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:aUda}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%daUaq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:daUa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%daaUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:daaU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
} }
SECTION("separator") SECTION("separator")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dUadq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUad}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dadUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dadU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%addUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:addU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
} }
@@ -534,43 +571,41 @@ TEST_CASE("repeated unit modifiers should throw", "[text][fmt][exception]")
TEST_CASE("more then one modifier of the same kind should throw", "[text][fmt][exception]") TEST_CASE("more then one modifier of the same kind should throw", "[text][fmt][exception]")
{ {
auto q = 1 * isq::length[m];
SECTION("text encoding") SECTION("text encoding")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%UdaAq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:UdaA}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dAaUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dAaU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dAUaq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dAUa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec"));
} }
SECTION("solidus") SECTION("solidus")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%aUdnq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:aUdn}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dnUaq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dnUa}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%daoUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:da1U}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec"));
} }
SECTION("separator") SECTION("separator")
{ {
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dUasq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUas}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%sadUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:sadU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%adsUq}", MP_UNITS_STD_FMT::make_format_args(q)), REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:adsU}", MP_UNITS_STD_FMT::make_format_args(m)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec"));
} }
@@ -578,23 +613,21 @@ TEST_CASE("more then one modifier of the same kind should throw", "[text][fmt][e
TEST_CASE("half_high_dot separator requested for ASCII encoding should throw", "[text][fmt][exception]") TEST_CASE("half_high_dot separator requested for ASCII encoding should throw", "[text][fmt][exception]")
{ {
auto q = 1 * isq::length[m]; REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dAa}", MP_UNITS_STD_FMT::make_format_args(m)),
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dAaq}", MP_UNITS_STD_FMT::make_format_args(q)),
MP_UNITS_STD_FMT::format_error, MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("half_high_dot unit separator allowed only for Unicode encoding")); Catch::Matchers::Message("half_high_dot unit separator allowed only for Unicode encoding"));
} }
TEST_CASE("%q and %Q can be put anywhere in a format string", "[text][fmt]") TEST_CASE("%U and %N can be put anywhere in a format string", "[text][fmt]")
{ {
SECTION("no space") { CHECK(MP_UNITS_STD_FMT::format("{:%Q%q}", 123 * isq::speed[km / h]) == "123km/h"); } SECTION("no space") { CHECK(MP_UNITS_STD_FMT::format("{:%N%U}", 123 * isq::speed[km / h]) == "123km/h"); }
SECTION("separator") { CHECK(MP_UNITS_STD_FMT::format("{:%Q###%q}", 123 * isq::speed[km / h]) == "123###km/h"); } SECTION("separator") { CHECK(MP_UNITS_STD_FMT::format("{:%N###%U}", 123 * isq::speed[km / h]) == "123###km/h"); }
SECTION("opposite order") { CHECK(MP_UNITS_STD_FMT::format("{:%q %Q}", 123 * isq::speed[km / h]) == "km/h 123"); } SECTION("opposite order") { CHECK(MP_UNITS_STD_FMT::format("{:%U %N}", 123 * isq::speed[km / h]) == "km/h 123"); }
} }
TEST_CASE("fill and align specification", "[text][fmt][ostream]") TEST_CASE("quantity fill and align specification", "[text][fmt][ostream]")
{ {
SECTION("ostream") SECTION("ostream")
{ {
@@ -649,40 +682,40 @@ TEST_CASE("fill and align specification", "[text][fmt][ostream]")
CHECK(MP_UNITS_STD_FMT::format("|{:*^10}|", 123 * isq::length[m]) == "|**123 m***|"); CHECK(MP_UNITS_STD_FMT::format("|{:*^10}|", 123 * isq::length[m]) == "|**123 m***|");
} }
SECTION("full format {:%Q %q} on a quantity") SECTION("full format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("|{:0%Q%q}|", 123 * isq::length[m]) == "|123m|"); CHECK(MP_UNITS_STD_FMT::format("|{:0%N%?%U}|", 123 * isq::length[m]) == "|123 m|");
CHECK(MP_UNITS_STD_FMT::format("|{:10%Q%q}|", 123 * isq::length[m]) == "| 123m|"); CHECK(MP_UNITS_STD_FMT::format("|{:10%N%?%U}|", 123 * isq::length[m]) == "| 123 m|");
CHECK(MP_UNITS_STD_FMT::format("|{:<10%Q%q}|", 123 * isq::length[m]) == "|123m |"); CHECK(MP_UNITS_STD_FMT::format("|{:<10%N%?%U}|", 123 * isq::length[m]) == "|123 m |");
CHECK(MP_UNITS_STD_FMT::format("|{:>10%Q%q}|", 123 * isq::length[m]) == "| 123m|"); CHECK(MP_UNITS_STD_FMT::format("|{:>10%N%?%U}|", 123 * isq::length[m]) == "| 123 m|");
CHECK(MP_UNITS_STD_FMT::format("|{:^10%Q%q}|", 123 * isq::length[m]) == "| 123m |"); CHECK(MP_UNITS_STD_FMT::format("|{:^10%N%?%U}|", 123 * isq::length[m]) == "| 123 m |");
CHECK(MP_UNITS_STD_FMT::format("|{:*<10%Q%q}|", 123 * isq::length[m]) == "|123m******|"); CHECK(MP_UNITS_STD_FMT::format("|{:*<10%N%?%U}|", 123 * isq::length[m]) == "|123 m*****|");
CHECK(MP_UNITS_STD_FMT::format("|{:*>10%Q%q}|", 123 * isq::length[m]) == "|******123m|"); CHECK(MP_UNITS_STD_FMT::format("|{:*>10%N%?%U}|", 123 * isq::length[m]) == "|*****123 m|");
CHECK(MP_UNITS_STD_FMT::format("|{:*^10%Q%q}|", 123 * isq::length[m]) == "|***123m***|"); CHECK(MP_UNITS_STD_FMT::format("|{:*^10%N%?%U}|", 123 * isq::length[m]) == "|**123 m***|");
} }
SECTION("value only format {:%Q} on a quantity") SECTION("value only format {:%N} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("|{:0%Q}|", 123 * isq::length[m]) == "|123|"); CHECK(MP_UNITS_STD_FMT::format("|{:0%N}|", 123 * isq::length[m]) == "|123|");
CHECK(MP_UNITS_STD_FMT::format("|{:10%Q}|", 123 * isq::length[m]) == "| 123|"); CHECK(MP_UNITS_STD_FMT::format("|{:10%N}|", 123 * isq::length[m]) == "| 123|");
CHECK(MP_UNITS_STD_FMT::format("|{:<10%Q}|", 123 * isq::length[m]) == "|123 |"); CHECK(MP_UNITS_STD_FMT::format("|{:<10%N}|", 123 * isq::length[m]) == "|123 |");
CHECK(MP_UNITS_STD_FMT::format("|{:>10%Q}|", 123 * isq::length[m]) == "| 123|"); CHECK(MP_UNITS_STD_FMT::format("|{:>10%N}|", 123 * isq::length[m]) == "| 123|");
CHECK(MP_UNITS_STD_FMT::format("|{:^10%Q}|", 123 * isq::length[m]) == "| 123 |"); CHECK(MP_UNITS_STD_FMT::format("|{:^10%N}|", 123 * isq::length[m]) == "| 123 |");
CHECK(MP_UNITS_STD_FMT::format("|{:*<10%Q}|", 123 * isq::length[m]) == "|123*******|"); CHECK(MP_UNITS_STD_FMT::format("|{:*<10%N}|", 123 * isq::length[m]) == "|123*******|");
CHECK(MP_UNITS_STD_FMT::format("|{:*>10%Q}|", 123 * isq::length[m]) == "|*******123|"); CHECK(MP_UNITS_STD_FMT::format("|{:*>10%N}|", 123 * isq::length[m]) == "|*******123|");
CHECK(MP_UNITS_STD_FMT::format("|{:*^10%Q}|", 123 * isq::length[m]) == "|***123****|"); CHECK(MP_UNITS_STD_FMT::format("|{:*^10%N}|", 123 * isq::length[m]) == "|***123****|");
} }
SECTION("symbol only format {:%q} on a quantity") SECTION("symbol only format {:%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("|{:0%q}|", 123 * isq::length[m]) == "|m|"); CHECK(MP_UNITS_STD_FMT::format("|{:0%U}|", 123 * isq::length[m]) == "|m|");
CHECK(MP_UNITS_STD_FMT::format("|{:10%q}|", 123 * isq::length[m]) == "| m|"); CHECK(MP_UNITS_STD_FMT::format("|{:10%U}|", 123 * isq::length[m]) == "| m|");
CHECK(MP_UNITS_STD_FMT::format("|{:<10%q}|", 123 * isq::length[m]) == "|m |"); CHECK(MP_UNITS_STD_FMT::format("|{:<10%U}|", 123 * isq::length[m]) == "|m |");
CHECK(MP_UNITS_STD_FMT::format("|{:>10%q}|", 123 * isq::length[m]) == "| m|"); CHECK(MP_UNITS_STD_FMT::format("|{:>10%U}|", 123 * isq::length[m]) == "| m|");
CHECK(MP_UNITS_STD_FMT::format("|{:^10%q}|", 123 * isq::length[m]) == "| m |"); CHECK(MP_UNITS_STD_FMT::format("|{:^10%U}|", 123 * isq::length[m]) == "| m |");
CHECK(MP_UNITS_STD_FMT::format("|{:*<10%q}|", 123 * isq::length[m]) == "|m*********|"); CHECK(MP_UNITS_STD_FMT::format("|{:*<10%U}|", 123 * isq::length[m]) == "|m*********|");
CHECK(MP_UNITS_STD_FMT::format("|{:*>10%q}|", 123 * isq::length[m]) == "|*********m|"); CHECK(MP_UNITS_STD_FMT::format("|{:*>10%U}|", 123 * isq::length[m]) == "|*********m|");
CHECK(MP_UNITS_STD_FMT::format("|{:*^10%q}|", 123 * isq::length[m]) == "|****m*****|"); CHECK(MP_UNITS_STD_FMT::format("|{:*^10%U}|", 123 * isq::length[m]) == "|****m*****|");
} }
} }
@@ -691,142 +724,139 @@ TEST_CASE("sign specification", "[text][fmt]")
auto inf = std::numeric_limits<double>::infinity() * si::metre; auto inf = std::numeric_limits<double>::infinity() * si::metre;
auto nan = std::numeric_limits<double>::quiet_NaN() * si::metre; auto nan = std::numeric_limits<double>::quiet_NaN() * si::metre;
SECTION("full format {:%Q %q} on a quantity") SECTION("full format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{0:%Q%q},{0:%+Q%q},{0:%-Q%q},{0:% Q%q}", 1 * isq::length[m]) == "1m,+1m,1m, 1m"); CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", 1 * isq::length[m]) ==
CHECK(MP_UNITS_STD_FMT::format("{0:%Q%q},{0:%+Q%q},{0:%-Q%q},{0:% Q%q}", -1 * isq::length[m]) == "-1m,-1m,-1m,-1m"); "1m,+1m,1m, 1m");
CHECK(MP_UNITS_STD_FMT::format("{0:%Q%q},{0:%+Q%q},{0:%-Q%q},{0:% Q%q}", inf) == "infm,+infm,infm, infm"); CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", -1 * isq::length[m]) ==
CHECK(MP_UNITS_STD_FMT::format("{0:%Q%q},{0:%+Q%q},{0:%-Q%q},{0:% Q%q}", nan) == "nanm,+nanm,nanm, nanm"); "-1m,-1m,-1m,-1m");
CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", inf) == "infm,+infm,infm, infm");
CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", nan) == "nanm,+nanm,nanm, nanm");
} }
SECTION("value only format {:%Q} on a quantity") SECTION("value only format {:%N} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{0:%Q},{0:%+Q},{0:%-Q},{0:% Q}", 1 * isq::length[m]) == "1,+1,1, 1"); CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", 1 * isq::length[m]) == "1,+1,1, 1");
CHECK(MP_UNITS_STD_FMT::format("{0:%Q},{0:%+Q},{0:%-Q},{0:% Q}", -1 * isq::length[m]) == "-1,-1,-1,-1"); CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", -1 * isq::length[m]) == "-1,-1,-1,-1");
CHECK(MP_UNITS_STD_FMT::format("{0:%Q},{0:%+Q},{0:%-Q},{0:% Q}", inf) == "inf,+inf,inf, inf"); CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", inf) == "inf,+inf,inf, inf");
CHECK(MP_UNITS_STD_FMT::format("{0:%Q},{0:%+Q},{0:%-Q},{0:% Q}", nan) == "nan,+nan,nan, nan"); CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", nan) == "nan,+nan,nan, nan");
} }
} }
TEST_CASE("precision specification", "[text][fmt]") TEST_CASE("precision specification", "[text][fmt]")
{ {
SECTION("full format {:%Q %q} on a quantity") SECTION("full format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%.0Q %q}", 1.2345 * isq::length[m]) == "1 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.0f}%?%U}", 1.2345 * isq::length[m]) == "1 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.1Q %q}", 1.2345 * isq::length[m]) == "1.2 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.1f}%?%U}", 1.2345 * isq::length[m]) == "1.2 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.2Q %q}", 1.2345 * isq::length[m]) == "1.23 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.2f}%?%U}", 1.2345 * isq::length[m]) == "1.23 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.3Q %q}", 1.2345 * isq::length[m]) == "1.234 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3f}%?%U}", 1.2345 * isq::length[m]) == "1.234 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.4Q %q}", 1.2345 * isq::length[m]) == "1.2345 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.4f}%?%U}", 1.2345 * isq::length[m]) == "1.2345 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.5Q %q}", 1.2345 * isq::length[m]) == "1.23450 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.5f}%?%U}", 1.2345 * isq::length[m]) == "1.23450 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.10Q %q}", 1.2345 * isq::length[m]) == "1.2345000000 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.10f}%?%U}", 1.2345 * isq::length[m]) == "1.2345000000 m");
} }
SECTION("value only format {:%Q} on a quantity") SECTION("value only format {:%N} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%.0Q}", 1.2345 * isq::length[m]) == "1"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.0f}}", 1.2345 * isq::length[m]) == "1");
CHECK(MP_UNITS_STD_FMT::format("{:%.1Q}", 1.2345 * isq::length[m]) == "1.2"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.1f}}", 1.2345 * isq::length[m]) == "1.2");
CHECK(MP_UNITS_STD_FMT::format("{:%.2Q}", 1.2345 * isq::length[m]) == "1.23"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.2f}}", 1.2345 * isq::length[m]) == "1.23");
CHECK(MP_UNITS_STD_FMT::format("{:%.3Q}", 1.2345 * isq::length[m]) == "1.234"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3f}}", 1.2345 * isq::length[m]) == "1.234");
CHECK(MP_UNITS_STD_FMT::format("{:%.4Q}", 1.2345 * isq::length[m]) == "1.2345"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.4f}}", 1.2345 * isq::length[m]) == "1.2345");
CHECK(MP_UNITS_STD_FMT::format("{:%.5Q}", 1.2345 * isq::length[m]) == "1.23450"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.5f}}", 1.2345 * isq::length[m]) == "1.23450");
CHECK(MP_UNITS_STD_FMT::format("{:%.10Q}", 1.2345 * isq::length[m]) == "1.2345000000"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.10f}}", 1.2345 * isq::length[m]) == "1.2345000000");
}
}
TEST_CASE("precision specification for integral representation should throw", "[text][fmt][exception]")
{
auto q = 1 * isq::length[m];
SECTION("full format {:%Q %q} on a quantity")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%.1Q %q}", MP_UNITS_STD_FMT::make_format_args(q)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("precision not allowed for integral quantity representation"));
}
SECTION("value only format {:%Q} on a quantity")
{
REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%.1Q}", MP_UNITS_STD_FMT::make_format_args(q)),
MP_UNITS_STD_FMT::format_error,
Catch::Matchers::Message("precision not allowed for integral quantity representation"));
} }
} }
TEST_CASE("type specification", "[text][fmt]") TEST_CASE("type specification", "[text][fmt]")
{ {
SECTION("full format {:%Q %q} on a quantity") SECTION("full format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%bQ %q}", 42 * isq::length[m]) == "101010 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:b}%?%U}", 42 * isq::length[m]) == "101010 m");
CHECK(MP_UNITS_STD_FMT::format("{:%BQ %q}", 42 * isq::length[m]) == "101010 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:B}%?%U}", 42 * isq::length[m]) == "101010 m");
CHECK(MP_UNITS_STD_FMT::format("{:%dQ %q}", 42 * isq::length[m]) == "42 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:d}%?%U}", 42 * isq::length[m]) == "42 m");
CHECK(MP_UNITS_STD_FMT::format("{:%oQ %q}", 42 * isq::length[m]) == "52 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:o}%?%U}", 42 * isq::length[m]) == "52 m");
CHECK(MP_UNITS_STD_FMT::format("{:%xQ %q}", 42 * isq::length[m]) == "2a m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:x}%?%U}", 42 * isq::length[m]) == "2a m");
CHECK(MP_UNITS_STD_FMT::format("{:%XQ %q}", 42 * isq::length[m]) == "2A m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:X}%?%U}", 42 * isq::length[m]) == "2A m");
CHECK(MP_UNITS_STD_FMT::format("{:%aQ %q}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); #if MP_UNITS_USE_FMTLIB
CHECK(MP_UNITS_STD_FMT::format("{:%.3aQ %q}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}%?%U}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m");
CHECK(MP_UNITS_STD_FMT::format("{:%AQ %q}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}%?%U}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.3AQ %q}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}%?%U}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m");
CHECK(MP_UNITS_STD_FMT::format("{:%eQ %q}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}%?%U}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.3eQ %q}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); #else
CHECK(MP_UNITS_STD_FMT::format("{:%EQ %q}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}%?%U}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.3EQ %q}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}%?%U}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m");
CHECK(MP_UNITS_STD_FMT::format("{:%gQ %q}", 1.2345678 * isq::length[m]) == "1.23457 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}%?%U}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m");
CHECK(MP_UNITS_STD_FMT::format("{:%gQ %q}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}%?%U}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.3gQ %q}", 1.2345678 * isq::length[m]) == "1.23 m"); #endif
CHECK(MP_UNITS_STD_FMT::format("{:%.3gQ %q}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:e}%?%U}", 1.2345678 * isq::length[m]) == "1.234568e+00 m");
CHECK(MP_UNITS_STD_FMT::format("{:%GQ %q}", 1.2345678 * isq::length[m]) == "1.23457 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3e}%?%U}", 1.2345678 * isq::length[m]) == "1.235e+00 m");
CHECK(MP_UNITS_STD_FMT::format("{:%GQ %q}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:E}%?%U}", 1.2345678 * isq::length[m]) == "1.234568E+00 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.3GQ %q}", 1.2345678 * isq::length[m]) == "1.23 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3E}%?%U}", 1.2345678 * isq::length[m]) == "1.235E+00 m");
CHECK(MP_UNITS_STD_FMT::format("{:%.3GQ %q}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}%?%U}", 1.2345678 * isq::length[m]) == "1.23457 m");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}%?%U}", 1.2345678 * isq::length[m]) == "1.23 m");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}%?%U}", 1.2345678 * isq::length[m]) == "1.23457 m");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}%?%U}", 1.2345678 * isq::length[m]) == "1.23 m");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m");
} }
SECTION("value only format {:%Q} on a quantity") SECTION("value only format {:%N} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%bQ}", 42 * isq::length[m]) == "101010"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:b}}", 42 * isq::length[m]) == "101010");
CHECK(MP_UNITS_STD_FMT::format("{:%BQ}", 42 * isq::length[m]) == "101010"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:B}}", 42 * isq::length[m]) == "101010");
CHECK(MP_UNITS_STD_FMT::format("{:%dQ}", 42 * isq::length[m]) == "42"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:d}}", 42 * isq::length[m]) == "42");
CHECK(MP_UNITS_STD_FMT::format("{:%oQ}", 42 * isq::length[m]) == "52"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:o}}", 42 * isq::length[m]) == "52");
CHECK(MP_UNITS_STD_FMT::format("{:%xQ}", 42 * isq::length[m]) == "2a"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:x}}", 42 * isq::length[m]) == "2a");
CHECK(MP_UNITS_STD_FMT::format("{:%XQ}", 42 * isq::length[m]) == "2A"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:X}}", 42 * isq::length[m]) == "2A");
CHECK(MP_UNITS_STD_FMT::format("{:%aQ}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); #if MP_UNITS_USE_FMTLIB
CHECK(MP_UNITS_STD_FMT::format("{:%.3aQ}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0");
CHECK(MP_UNITS_STD_FMT::format("{:%AQ}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0");
CHECK(MP_UNITS_STD_FMT::format("{:%.3AQ}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0");
CHECK(MP_UNITS_STD_FMT::format("{:%eQ}", 1.2345678 * isq::length[m]) == "1.234568e+00"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0");
CHECK(MP_UNITS_STD_FMT::format("{:%.3eQ}", 1.2345678 * isq::length[m]) == "1.235e+00"); #else
CHECK(MP_UNITS_STD_FMT::format("{:%EQ}", 1.2345678 * isq::length[m]) == "1.234568E+00"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0");
CHECK(MP_UNITS_STD_FMT::format("{:%.3EQ}", 1.2345678 * isq::length[m]) == "1.235E+00"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}}", 1.2345678 * isq::length[m]) == "1.3c1p+0");
CHECK(MP_UNITS_STD_FMT::format("{:%gQ}", 1.2345678 * isq::length[m]) == "1.23457"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0");
CHECK(MP_UNITS_STD_FMT::format("{:%gQ}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}}", 1.2345678 * isq::length[m]) == "1.3C1P+0");
CHECK(MP_UNITS_STD_FMT::format("{:%.3gQ}", 1.2345678 * isq::length[m]) == "1.23"); #endif
CHECK(MP_UNITS_STD_FMT::format("{:%.3gQ}", 1.2345678e8 * isq::length[m]) == "1.23e+08"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:e}}", 1.2345678 * isq::length[m]) == "1.234568e+00");
CHECK(MP_UNITS_STD_FMT::format("{:%GQ}", 1.2345678 * isq::length[m]) == "1.23457"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3e}}", 1.2345678 * isq::length[m]) == "1.235e+00");
CHECK(MP_UNITS_STD_FMT::format("{:%GQ}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:E}}", 1.2345678 * isq::length[m]) == "1.234568E+00");
CHECK(MP_UNITS_STD_FMT::format("{:%.3GQ}", 1.2345678 * isq::length[m]) == "1.23"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3E}}", 1.2345678 * isq::length[m]) == "1.235E+00");
CHECK(MP_UNITS_STD_FMT::format("{:%.3GQ}", 1.2345678e8 * isq::length[m]) == "1.23E+08"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}}", 1.2345678 * isq::length[m]) == "1.23457");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}}", 1.2345678e8 * isq::length[m]) == "1.23457e+08");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}}", 1.2345678 * isq::length[m]) == "1.23");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}}", 1.2345678e8 * isq::length[m]) == "1.23e+08");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}}", 1.2345678 * isq::length[m]) == "1.23457");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}}", 1.2345678e8 * isq::length[m]) == "1.23457E+08");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}}", 1.2345678 * isq::length[m]) == "1.23");
CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}}", 1.2345678e8 * isq::length[m]) == "1.23E+08");
} }
} }
TEST_CASE("different base types with the # specifier", "[text][fmt]") TEST_CASE("different base types with the # specifier", "[text][fmt]")
{ {
SECTION("full format {:%Q %q} on a quantity") SECTION("full format {:%N%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%#bQ %q}", 42 * isq::length[m]) == "0b101010 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#b}%?%U}", 42 * isq::length[m]) == "0b101010 m");
CHECK(MP_UNITS_STD_FMT::format("{:%#BQ %q}", 42 * isq::length[m]) == "0B101010 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#B}%?%U}", 42 * isq::length[m]) == "0B101010 m");
CHECK(MP_UNITS_STD_FMT::format("{:%#oQ %q}", 42 * isq::length[m]) == "052 m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#o}%?%U}", 42 * isq::length[m]) == "052 m");
CHECK(MP_UNITS_STD_FMT::format("{:%#xQ %q}", 42 * isq::length[m]) == "0x2a m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#x}%?%U}", 42 * isq::length[m]) == "0x2a m");
CHECK(MP_UNITS_STD_FMT::format("{:%#XQ %q}", 42 * isq::length[m]) == "0X2A m"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#X}%?%U}", 42 * isq::length[m]) == "0X2A m");
} }
SECTION("value only format {:%Q} on a quantity") SECTION("value only format {:%N} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format("{:%#bQ}", 42 * isq::length[m]) == "0b101010"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#b}}", 42 * isq::length[m]) == "0b101010");
CHECK(MP_UNITS_STD_FMT::format("{:%#BQ}", 42 * isq::length[m]) == "0B101010"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#B}}", 42 * isq::length[m]) == "0B101010");
CHECK(MP_UNITS_STD_FMT::format("{:%#oQ}", 42 * isq::length[m]) == "052"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#o}}", 42 * isq::length[m]) == "052");
CHECK(MP_UNITS_STD_FMT::format("{:%#xQ}", 42 * isq::length[m]) == "0x2a"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#x}}", 42 * isq::length[m]) == "0x2a");
CHECK(MP_UNITS_STD_FMT::format("{:%#XQ}", 42 * isq::length[m]) == "0X2A"); CHECK(MP_UNITS_STD_FMT::format("{:{%N:#X}}", 42 * isq::length[m]) == "0X2A");
} }
} }
@@ -845,10 +875,10 @@ TEST_CASE("localization with the 'L' specifier", "[text][fmt][localization]")
std::locale grp2{std::locale::classic(), new group2}; std::locale grp2{std::locale::classic(), new group2};
std::locale grp3{std::locale::classic(), new group3}; std::locale grp3{std::locale::classic(), new group3};
SECTION("full format {:%LQ %q} on a quantity") SECTION("full format {:{%N:L}%?%U} on a quantity")
{ {
CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%LQ %q}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); CHECK(MP_UNITS_STD_FMT::format(grp2, "{:{%N:L}%?%U}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s");
CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%LQ %q}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); CHECK(MP_UNITS_STD_FMT::format(grp3, "{:{%N:L}%?%U}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s");
} }
} }

View File

@@ -25,7 +25,6 @@ import os
from conan import ConanFile from conan import ConanFile
from conan.tools.build import can_run from conan.tools.build import can_run
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
from conan.tools.scm import Version
class TestPackageConan(ConanFile): class TestPackageConan(ConanFile):