Merge branch 'master' into find-idx

This commit is contained in:
ToruNiina 2020-01-07 22:13:15 +09:00
commit 827b433389
21 changed files with 910 additions and 288 deletions

View File

@ -12,7 +12,7 @@ jobs:
command: |
g++ --version
cd tests/
g++ -std=c++11 -O2 -Wall -Wextra -Werror -I../ check_toml_test.cpp -o check_toml_test
g++ -std=c++11 -O2 -Wall -Wextra -Werror -DTOML11_COLORIZE_ERROR_MESSAGE -I../ check_toml_test.cpp -o check_toml_test
go get github.com/BurntSushi/toml-test
$GOPATH/bin/toml-test ./check_toml_test
test_serialization:
@ -24,7 +24,7 @@ jobs:
command: |
g++ --version
cd tests/
g++ -std=c++11 -O2 -Wall -Wextra -Wpedantic -Werror -I../ check_serialization.cpp -o check_serialization
g++ -std=c++11 -O2 -Wall -Wextra -Wpedantic -Werror -DTOML11_COLORIZE_ERROR_MESSAGE -I../ check_serialization.cpp -o check_serialization
git clone https://github.com/BurntSushi/toml-test.git
cp check_serialization toml-test/tests/valid
cd toml-test/tests/valid
@ -47,7 +47,7 @@ jobs:
command: |
g++ --version
cd tests/
g++ -std=c++11 -O2 -Wall -Wextra -Wpedantic -Werror -I../ check.cpp -o check
g++ -std=c++11 -O2 -Wall -Wextra -Wpedantic -Werror -DTOML11_COLORIZE_ERROR_MESSAGE -I../ check.cpp -o check
git clone https://github.com/BurntSushi/toml-test.git
cp check toml-test/tests/invalid
cp check toml-test/tests/valid

View File

@ -9,7 +9,7 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
packages:
- g++-5
@ -21,7 +21,7 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
packages:
- g++-6
@ -33,7 +33,7 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
packages:
- g++-7
@ -45,7 +45,7 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
packages:
- g++-8
@ -57,7 +57,7 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
packages:
- g++-8
@ -69,7 +69,7 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
packages:
- g++-8
@ -81,7 +81,7 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
packages:
- g++-8
@ -89,16 +89,16 @@ matrix:
- os: linux
language: cpp
compiler: clang
env: COMPILER="clang++-3.7" CXX_STANDARD=11 TOML_HEAD=OFF
env: COMPILER="clang++-3.9" CXX_STANDARD=11 TOML_HEAD=OFF
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.7
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-3.9
packages:
- g++-8
- clang-3.7
- clang-3.9
- boost1.70
- os: linux
language: cpp
@ -107,9 +107,9 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-4.0
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-4.0
packages:
- g++-8
- clang-4.0
@ -121,9 +121,9 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-5.0
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-5.0
packages:
- g++-8
- clang-5.0
@ -135,9 +135,9 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-6.0
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-6.0
packages:
- g++-8
- clang-6.0
@ -149,9 +149,9 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-7
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-7
packages:
- g++-8
- clang-7
@ -163,9 +163,9 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-8
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-8
packages:
- g++-8
- clang-8
@ -177,9 +177,9 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-8
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-8
packages:
- g++-8
- clang-8
@ -191,9 +191,9 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-8
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-8
packages:
- clang-8
- g++-8
@ -205,9 +205,9 @@ matrix:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-8
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:mhier/libboost-latest'
- llvm-toolchain-trusty-8
packages:
- clang-8
- g++-8
@ -216,6 +216,15 @@ matrix:
language: cpp
compiler: clang
env: CXX_STANDARD=11
cache:
directories:
- $HOME/Library/Caches/Homebrew
addons:
homebrew:
update: true
packages:
- cmake
- boost
script:
- |
@ -224,8 +233,6 @@ script:
travis_retry wget "https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5-Linux-x86_64.tar.gz"
tar xf cmake-3.14.5-Linux-x86_64.tar.gz -C cmake --strip-components=1
export PATH=${TRAVIS_BUILD_DIR}/cmake/bin:${PATH}
else
brew upgrade cmake boost
fi
- cmake --version
- mkdir build
@ -234,3 +241,10 @@ script:
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_CXX_STANDARD=$CXX_STANDARD -DTOML11_USE_UNRELEASED_TOML_FEATURES=${TOML_HEAD} ..
- make
- ctest --output-on-failure
# https://stackoverflow.com/a/53331571
before_cache:
- |
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
brew cleanup
fi

View File

@ -4,8 +4,8 @@ enable_testing()
project(toml11)
set(toml11_VERSION_MAYOR 3)
set(toml11_VERSION_MINOR 0)
set(toml11_VERSION_PATCH 1)
set(toml11_VERSION_MINOR 2)
set(toml11_VERSION_PATCH 0)
set(toml11_VERSION
"${toml11_VERSION_MAYOR}.${toml11_VERSION_MINOR}.${toml11_VERSION_PATCH}"
)

235
README.md
View File

@ -68,6 +68,9 @@ int main()
- [TOML literal](#toml-literal)
- [Conversion between toml value and arbitrary types](#conversion-between-toml-value-and-arbitrary-types)
- [Formatting user-defined error messages](#formatting-user-defined-error-messages)
- [Obtaining location information](#obtaining-location-information)
- [Exceptions](#exceptions)
- [Colorize Error Messages](#colorize-error-messages)
- [Serializing TOML data](#serializing-toml-data)
- [Underlying types](#underlying-types)
- [Unreleased TOML features](#unreleased-toml-features)
@ -351,6 +354,7 @@ The above code works with the following toml file.
# NOT {"physical": {"color": "orange"}}.
```
## Casting a toml value
### `toml::get`
@ -470,6 +474,44 @@ If an invalid key (integer for a table, string for an array), it throws
`toml::type_error` for the conversion. If the provided key is out-of-range,
it throws `std::out_of_range`.
Note that, although `std::string` has `at()` member function, `toml::value::at`
throws if the contained type is a string. Because `std::string` does not
contain `toml::value`.
### `operator[]`
You can also access to the element of a table and an array by
`toml::basic_value::operator[]`.
```cpp
const toml::value v{1,2,3,4,5};
std::cout << v[2].as_integer() << std::endl; // 3
const toml::value v{{"foo", 42}, {"bar", 3.14}};
std::cout << v["foo"].as_integer() << std::endl; // 42
```
When you access to a `toml::value` that is not initialized yet via
`operator[](const std::string&)`, the `toml::value` will be a table,
just like the `std::map`.
```cpp
toml::value v; // not initialized as a table.
v["foo"] = 42; // OK. `v` will be a table.
```
Contrary, if you access to a `toml::value` that contains an array via `operator[]`,
it does not check anything. It converts `toml::value` without type check and then
access to the n-th element without boundary check, just like the `std::vector::operator[]`.
```cpp
toml::value v; // not initialized as an array
v[2] = 42; // error! UB
```
Please make sure that the `toml::value` has an array inside when you access to
its element via `operator[]`.
## Checking value type
You can check the type of a value by `is_xxx` function.
@ -692,6 +734,7 @@ date information, but it can be converted to `std::chrono::duration` that
represents a duration from the beginning of the day, `00:00:00.000`.
```toml
# sample.toml
date = 2018-12-23
time = 12:30:00
l_dt = 2018-12-23T12:30:00
@ -708,8 +751,12 @@ const auto o_dt = toml::get<std::chrono::system_clock::time_point>(data.at("o_dt
const auto time = toml::get<std::chrono::minutes>(data.at("time")); // 12 * 60 + 30 min
```
toml11 defines its own datetime classes.
You can see the definitions in [toml/datetime.hpp](toml/datetime.hpp).
`local_date` and `local_datetime` are assumed to be in the local timezone when
they are converted into `time_point`. On the other hand, `offset_datetime` only
uses the offset part of the data and it does not take local timezone into account.
To contain datetime data, toml11 defines its own datetime types.
For more detail, you can see the definitions in [toml/datetime.hpp](toml/datetime.hpp).
## Getting with a fallback
@ -1254,7 +1301,28 @@ you will get an error message like this.
| ~~ maximum number here
```
### Obtaining location information
You can print hints at the end of the message.
```cpp
std::vector<std::string> hints;
hints.push_back("positive number means n >= 0.");
hints.push_back("negative number is not positive.");
std::cerr << toml::format_error("[error] value should be positive",
data.at("num"), "positive number required", hints)
<< std::endl;
```
```console
[error] value should be positive
--> example.toml
2 | num = 42
| ~~ positive number required
|
Hint: positive number means n >= 0.
Hint: negative number is not positive.
```
## Obtaining location information
You can also format error messages in your own way by using `source_location`.
@ -1279,6 +1347,92 @@ const toml::value v = /*...*/;
const toml::source_location loc = v.location();
```
## Exceptions
All the exceptions thrown by toml11 inherits `toml::exception` that inherits
`std::exception`.
```cpp
namespace toml {
struct exception : public std::exception {/**/};
struct syntax_error : public toml::exception {/**/};
struct type_error : public toml::exception {/**/};
struct internal_error : public toml::exception {/**/};
} // toml
```
`toml::exception` has `toml::exception::location()` member function that returns
`toml::source_location`, in addition to `what()`.
```cpp
namespace toml {
struct exception : public std::exception
{
// ...
source_location const& location() const noexcept;
};
} // toml
```
It represents where the error occurs.
## Colorize Error Messages
By defining `TOML11_COLORIZE_ERROR_MESSAGE`, the error messages from
`toml::parse` and `toml::find|get` will be colorized. By default, this feature
is turned off.
With the following toml file taken from `toml-lang/toml/tests/hard_example.toml`,
```toml
[error]
array = [
"This might most likely happen in multiline arrays",
Like here,
"or here,
and here"
] End of array comment, forgot the #
```
the error message would be like this.
![error-message-1](https://github.com/ToruNiina/toml11/blob/misc/misc/toml11-err-msg-1.png)
With the following,
```toml
[error]
# array = [
# "This might most likely happen in multiline arrays",
# Like here,
# "or here,
# and here"
# ] End of array comment, forgot the #
number = 3.14 pi <--again forgot the #
```
the error message would be like this.
![error-message-2](https://github.com/ToruNiina/toml11/blob/misc/misc/toml11-err-msg-2.png)
The message would be messy when it is written to a file, not a terminal because
it uses [ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code).
Without `TOML11_COLORIZE_ERROR_MESSAGE`, you can still colorize user-defined
error message by passing `true` to the `toml::format_error` function.
If you define `TOML11_COLORIZE_ERROR_MESSAGE`, the value is `true` by default.
If not, the defalut value would be `false`.
```cpp
std::cerr << toml::format_error("[error] value should be positive",
data.at("num"), "positive number required",
hints, /*colorize = */ true) << std::endl;
```
Note: It colorize `[error]` in red. That means that it detects `[error]` prefix
at the front of the error message. If there is no `[error]` prefix,
`format_error` adds it to the error message.
## Serializing TOML data
toml11 enables you to serialize data into toml format.
@ -1411,17 +1565,83 @@ This feature is introduced to make it easy to write a custom serializer.
Because `std::chrono::system_clock::time_point` is a __time point__,
not capable of representing a Local Time independent from a specific day.
It is recommended to get `datetime`s as `std::chrono` classes through `toml::get`.
## Unreleased TOML features
There are some unreleased features in toml-lang/toml:master.
Currently, the following features are available after defining
`TOML11_USE_UNRELEASED_TOML_FEATURES` macro flag.
To use those features, `#define` `TOML11_USE_UNRELEASED_TOML_FEATURES` before
including `toml.hpp` or pass `-DTOML11_USE_UNRELEASED_TOML_FEATURES` to your
compiler.
- Leading zeroes in exponent parts of floats are permitted.
- e.g. `1.0e+01`, `5e+05`
- [toml-lang/toml/PR/656](https://github.com/toml-lang/toml/pull/656)
- Allow raw tab characters in basic strings and multi-line basic strings.
- [toml-lang/toml/PR/627](https://github.com/toml-lang/toml/pull/627)
- Allow heterogeneous arrays
- [toml-lang/toml/PR/676](https://github.com/toml-lang/toml/pull/676)
### Note about heterogeneous arrays
Although `toml::parse` allows heterogeneous arrays, constructor of `toml::value`
does not.
```cpp
// this won't be compiled
toml::value v{
"foo", 3.14, 42, {1,2,3,4,5}, {{"key", "value"}}
}
```
There is a workaround for this issue. By explicitly converting values into
`toml::value`, you can initialize `toml::value` with a heterogeneous array.
```cpp
// OK!
toml::value v{
toml::value("foo"), toml::value(3.14), toml::value(42),
toml::value{1,2,3,4,5}, toml::value{{"key", "value"}}
}
```
The reason why the first example is not allowed is the following.
Let's assume that you are initializing a `toml::value` with a table.
```cpp
// # expecting TOML table.
toml::value v{ // [v]
{"answer", 42}, // answer = 42
{"pi", 3.14}, // pi = 3.14
{"foo", "bar"} // foo = "bar"
};
```
This is indistinguishable from a (heterogeneous) TOML array definition.
```toml
v = [
["answer", 42],
["pi", 3.14],
["foo", "bar"],
]
```
This means that the above C++ code makes constructor's overload resolution
ambiguous. So a constructor that allows both "table as an initializer-list" and
"heterogeneous array as an initializer-list" cannot be implemented.
Thus, although it is painful, you need to explicitly cast values into
`toml::value` when you initialize heterogeneous array in C++ code.
```cpp
// You need to do this when you want to initialize hetero array.
toml::value v{
toml::value("foo"), toml::value(3.14), toml::value(42),
toml::value{1,2,3,4,5}, toml::value{{"key", "value"}}
}
```
## Breaking Changes from v2
@ -1494,6 +1714,11 @@ I appreciate the help of the contributors who introduced the great feature to th
- Added installation script to CMake
- J.C. Moyer (@jcmoyer)
- Fixed an example code in the documentation
- Jt Freeman (@blockparty-sh)
- Fixed feature test macro around `localtime_s`
- Suppress warnings in Debug mode
- OGAWA Kenichi (@kenichiice)
- Suppress warnings on intel compiler
## Licensing terms

View File

@ -80,10 +80,14 @@ BOOST_AUTO_TEST_CASE(test_detect_conflicting_value)
BOOST_AUTO_TEST_CASE(test_detect_inhomogeneous_array)
{
#ifdef TOML11_USE_UNRELEASED_TOML_FEATURES
BOOST_TEST_MESSAGE("heterogeneous array will be allowed in the next release");
#else
std::istringstream stream(std::string(
"a = [1, 1.0]\n"
));
BOOST_CHECK_THROW(toml::parse(stream), toml::syntax_error);
#endif
}
BOOST_AUTO_TEST_CASE(test_detect_appending_array_of_table)

View File

@ -128,3 +128,47 @@ BOOST_AUTO_TEST_CASE(test_multiline_array_value)
TOML11_TEST_PARSE_EQUAL_VALUE(parse_value<toml::value>, "[\"foo\",#comment\n\"b#r\",#comment\n\"b#z\"#comment\n]", toml::value(a));
}
}
BOOST_AUTO_TEST_CASE(test_heterogeneous_array)
{
#ifndef TOML11_USE_UNRELEASED_TOML_FEATURES
BOOST_TEST_MESSAGE("In strict TOML v0.5.0, heterogeneous arrays are not allowed.");
#else
{
array a(5);
a[0] = toml::value("foo");
a[1] = toml::value(3.14);
a[2] = toml::value(42);
a[3] = toml::value{toml::value("array"), toml::value("of"), toml::value("hetero-array"), toml::value(1)};
a[4] = toml::value{{"key", "value"}};
TOML11_TEST_PARSE_EQUAL_VALUE(parse_value<toml::value>, "[\"foo\", 3.14, 42, [\"array\", \"of\", \"hetero-array\", 1], {key = \"value\"}]", toml::value(a));
}
{
array a(5);
a[0] = toml::value("foo");
a[1] = toml::value(3.14);
a[2] = toml::value(42);
a[3] = toml::value{toml::value("array"), toml::value("of"), toml::value("hetero-array"), toml::value(1)};
a[4] = toml::value{{"key", "value"}};
TOML11_TEST_PARSE_EQUAL_VALUE(parse_value<toml::value>, "[\"foo\",\n 3.14,\n 42,\n [\"array\", \"of\", \"hetero-array\", 1],\n {key = \"value\"},\n]", toml::value(a));
}
{
array a(5);
a[0] = toml::value("foo");
a[1] = toml::value(3.14);
a[2] = toml::value(42);
a[3] = toml::value{toml::value("array"), toml::value("of"), toml::value("hetero-array"), toml::value(1)};
a[4] = toml::value{{"key", "value"}};
TOML11_TEST_PARSE_EQUAL_VALUE(parse_value<toml::value>, "[\"foo\",#comment\n 3.14,#comment\n 42,#comment\n [\"array\", \"of\", \"hetero-array\", 1],#comment\n {key = \"value\"},#comment\n]#comment", toml::value(a));
}
{
array a(5);
a[0] = toml::value("foo");
a[1] = toml::value(3.14);
a[2] = toml::value(42);
a[3] = toml::value{toml::value("array"), toml::value("of"), toml::value("hetero-array"), toml::value(1)};
a[4] = toml::value{{"key", "value"}};
TOML11_TEST_PARSE_EQUAL_VALUE(parse_value<toml::value>, "[\"foo\",\n 3.14,\n 42,\n [\"array\",\n \"of\",\n \"hetero-array\",\n 1],\n {key = \"value\"},\n]", toml::value(a));
}
#endif
}

View File

@ -111,3 +111,44 @@ BOOST_AUTO_TEST_CASE(test_literal_ml_string)
}
}
BOOST_AUTO_TEST_CASE(test_string_add_assign)
{
// string literal
{
toml::string str("foo");
str += "bar";
BOOST_TEST(str.str == "foobar");
}
// std::string
{
toml::string str("foo");
std::string str2("bar");
str += str2;
BOOST_TEST(str.str == "foobar");
}
// toml::string
{
toml::string str("foo");
toml::string str2("bar");
str += str2;
BOOST_TEST(str.str == "foobar");
}
#if __cplusplus >= 201703L
// std::string_view
{
toml::string str("foo");
str += std::string_view("bar");
BOOST_TEST(str == "foobar");
}
#endif
// std::string += toml::string
{
std::string str("foo");
toml::string str2("bar");
str += str2;
BOOST_TEST(str == "foobar");
}
}

View File

@ -934,3 +934,35 @@ BOOST_AUTO_TEST_CASE(test_value_at)
BOOST_CHECK_THROW(v1.at(5), std::out_of_range);
}
}
BOOST_AUTO_TEST_CASE(test_value_bracket)
{
{
toml::value v1{{"foo", 42}, {"bar", 3.14}, {"baz", "qux"}};
BOOST_TEST(v1["foo"].as_integer() == 42);
BOOST_TEST(v1["bar"].as_floating() == 3.14);
BOOST_TEST(v1["baz"].as_string() == "qux");
v1["qux"] = 54;
BOOST_TEST(v1["qux"].as_integer() == 54);
}
{
toml::value v1;
v1["foo"] = 42;
BOOST_TEST(v1.is_table());
BOOST_TEST(v1["foo"].as_integer() == 42);
}
{
toml::value v1{1,2,3,4,5};
BOOST_TEST(v1[0].as_integer() == 1);
BOOST_TEST(v1[1].as_integer() == 2);
BOOST_TEST(v1[2].as_integer() == 3);
BOOST_TEST(v1[3].as_integer() == 4);
BOOST_TEST(v1[4].as_integer() == 5);
BOOST_CHECK_THROW(v1["foo"], toml::type_error);
}
}

64
toml/color.hpp Normal file
View File

@ -0,0 +1,64 @@
#ifndef TOML11_COLOR_HPP
#define TOML11_COLOR_HPP
#include <ostream>
#include <cstdint>
#ifdef TOML11_COLORIZE_ERROR_MESSAGE
#define TOML11_ERROR_MESSAGE_COLORIZED true
#else
#define TOML11_ERROR_MESSAGE_COLORIZED false
#endif
namespace toml
{
// put ANSI escape sequence to ostream
namespace color_ansi
{
namespace detail
{
inline int colorize_index()
{
static const int index = std::ios_base::xalloc();
return index;
}
} // detail
inline std::ostream& colorize(std::ostream& os)
{
// by default, it is zero.
os.iword(detail::colorize_index()) = 1;
return os;
}
inline std::ostream& nocolorize(std::ostream& os)
{
os.iword(detail::colorize_index()) = 0;
return os;
}
inline std::ostream& reset (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[00m";} return os;}
inline std::ostream& bold (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[01m";} return os;}
inline std::ostream& grey (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[30m";} return os;}
inline std::ostream& red (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[31m";} return os;}
inline std::ostream& green (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[32m";} return os;}
inline std::ostream& yellow (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[33m";} return os;}
inline std::ostream& blue (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[34m";} return os;}
inline std::ostream& magenta(std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[35m";} return os;}
inline std::ostream& cyan (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[36m";} return os;}
inline std::ostream& white (std::ostream& os)
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[37m";} return os;}
} // color_ansi
// ANSI escape sequence is the only and default colorization method currently
namespace color = color_ansi;
} // toml
#endif// TOML11_COLOR_HPP

View File

@ -45,6 +45,7 @@ inline std::string show_char(const char c)
buf.fill('\0');
const auto r = std::snprintf(
buf.data(), buf.size(), "0x%02x", static_cast<int>(c) & 0xFF);
(void) r; // Unused variable warning
assert(r == static_cast<int>(buf.size()) - 1);
return std::string(buf.data());
}

View File

@ -20,7 +20,7 @@ namespace toml
namespace detail
{
// TODO: find more sophisticated way to handle this
#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE || _POSIX_SOURCE
#if _POSIX_C_SOURCE >= 1 || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE)
inline std::tm localtime_s(const std::time_t* src)
{
std::tm dst;
@ -28,6 +28,13 @@ inline std::tm localtime_s(const std::time_t* src)
if (!result) { throw std::runtime_error("localtime_r failed."); }
return dst;
}
inline std::tm gmtime_s(const std::time_t* src)
{
std::tm dst;
const auto result = ::gmtime_r(src, &dst);
if (!result) { throw std::runtime_error("gmtime_r failed."); }
return dst;
}
#elif _MSC_VER
inline std::tm localtime_s(const std::time_t* src)
{
@ -36,13 +43,26 @@ inline std::tm localtime_s(const std::time_t* src)
if (result) { throw std::runtime_error("localtime_s failed."); }
return dst;
}
#else
inline std::tm gmtime_s(const std::time_t* src)
{
std::tm dst;
const auto result = ::gmtime_s(&dst, src);
if (result) { throw std::runtime_error("gmtime_s failed."); }
return dst;
}
#else // fallback. not threadsafe
inline std::tm localtime_s(const std::time_t* src)
{
const auto result = std::localtime(src);
if (!result) { throw std::runtime_error("localtime failed."); }
return *result;
}
inline std::tm gmtime_s(const std::time_t* src)
{
const auto result = std::gmtime(src);
if (!result) { throw std::runtime_error("gmtime failed."); }
return *result;
}
#endif
} // detail
@ -378,10 +398,31 @@ struct local_datetime
{
using internal_duration =
typename std::chrono::system_clock::time_point::duration;
// Normally DST begins at A.M. 3 or 4. If we re-use conversion operator
// of local_date and local_time independently, the conversion fails if
// it is the day when DST begins or ends. Since local_date considers the
// time is 00:00 A.M. and local_time does not consider DST because it
// does not have any date information. We need to consider both date and
// time information at the same time to convert it correctly.
std::tm t;
t.tm_sec = static_cast<int>(this->time.second);
t.tm_min = static_cast<int>(this->time.minute);
t.tm_hour = static_cast<int>(this->time.hour);
t.tm_mday = static_cast<int>(this->date.day);
t.tm_mon = static_cast<int>(this->date.month);
t.tm_year = static_cast<int>(this->date.year) - 1900;
t.tm_wday = 0; // the value will be ignored
t.tm_yday = 0; // the value will be ignored
t.tm_isdst = -1;
// std::mktime returns date as local time zone. no conversion needed
auto dt = std::chrono::system_clock::time_point(this->date);
auto dt = std::chrono::system_clock::from_time_t(std::mktime(&t));
dt += std::chrono::duration_cast<internal_duration>(
std::chrono::nanoseconds(this->time));
std::chrono::milliseconds(this->time.millisecond) +
std::chrono::microseconds(this->time.microsecond) +
std::chrono::nanoseconds (this->time.nanosecond));
return dt;
}
@ -447,40 +488,71 @@ struct offset_datetime
: date(dt.date), time(dt.time), offset(o)
{}
explicit offset_datetime(const local_datetime& ld)
: date(ld.date), time(ld.time), offset(get_local_offset())
: date(ld.date), time(ld.time), offset(get_local_offset(nullptr))
// use the current local timezone offset
{}
explicit offset_datetime(const std::chrono::system_clock::time_point& tp)
: offset_datetime(local_datetime(tp))
{}
: offset(0, 0) // use gmtime
{
const auto timet = std::chrono::system_clock::to_time_t(tp);
const auto tm = detail::gmtime_s(&timet);
this->date = local_date(tm);
this->time = local_time(tm);
}
explicit offset_datetime(const std::time_t& t)
: offset_datetime(local_datetime(t))
{}
: offset(0, 0) // use gmtime
{
const auto tm = detail::gmtime_s(&t);
this->date = local_date(tm);
this->time = local_time(tm);
}
explicit offset_datetime(const std::tm& t)
: offset_datetime(local_datetime(t))
{}
: offset(0, 0) // assume gmtime
{
this->date = local_date(t);
this->time = local_time(t);
}
operator std::chrono::system_clock::time_point() const
{
// get date-time
using internal_duration =
typename std::chrono::system_clock::time_point::duration;
std::chrono::system_clock::time_point tp =
std::chrono::system_clock::time_point(this->date) +
std::chrono::duration_cast<internal_duration>(
std::chrono::nanoseconds(this->time));
// get date-time in UTC. let's say we are in +09:00 (JPN).
// writing 12:00:00 in +09:00 means 03:00:00Z. to represent
// 12:00:00Z, first we need to add +09:00.
const auto ofs = get_local_offset();
// first, convert it to local date-time information in the same way as
// local_datetime does. later we will use time_t to adjust time offset.
std::tm t;
t.tm_sec = static_cast<int>(this->time.second);
t.tm_min = static_cast<int>(this->time.minute);
t.tm_hour = static_cast<int>(this->time.hour);
t.tm_mday = static_cast<int>(this->date.day);
t.tm_mon = static_cast<int>(this->date.month);
t.tm_year = static_cast<int>(this->date.year) - 1900;
t.tm_wday = 0; // the value will be ignored
t.tm_yday = 0; // the value will be ignored
t.tm_isdst = -1;
const std::time_t tp_loc = std::mktime(std::addressof(t));
auto tp = std::chrono::system_clock::from_time_t(tp_loc);
tp += std::chrono::duration_cast<internal_duration>(
std::chrono::milliseconds(this->time.millisecond) +
std::chrono::microseconds(this->time.microsecond) +
std::chrono::nanoseconds (this->time.nanosecond));
// Since mktime uses local time zone, it should be corrected.
// `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if
// we are in `+09:00` timezone. To represent `12:00:00Z` there, we need
// to add `+09:00` to `03:00:00Z`.
// Here, it uses the time_t converted from date-time info to handle
// daylight saving time.
const auto ofs = get_local_offset(std::addressof(tp_loc));
tp += std::chrono::hours (ofs.hour);
tp += std::chrono::minutes(ofs.minute);
// here, tp represents 12:00:00 in UTC but we have offset information.
// we need to subtract it. For example, let's say the input is
// 12:00:00-08:00. now we have tp = 12:00:00Z as a result of the above
// conversion. But the actual time we need to return is 20:00:00Z
// because of -08:00.
// We got `12:00:00Z` by correcting local timezone applied by mktime.
// Then we will apply the offset. Let's say `12:00:00-08:00` is given.
// And now, we have `12:00:00Z`. `12:00:00-08:00` means `20:00:00Z`.
// So we need to subtract the offset.
tp -= std::chrono::minutes(this->offset);
return tp;
}
@ -500,11 +572,10 @@ struct offset_datetime
private:
static time_offset get_local_offset()
static time_offset get_local_offset(const std::time_t* tp)
{
// get current timezone
const auto tmp1 = std::time(nullptr);
const auto t = detail::localtime_s(&tmp1);
// get local timezone with the same date-time information as mktime
const auto t = detail::localtime_s(tp);
std::array<char, 6> buf;
const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0

View File

@ -2,6 +2,7 @@
// Distributed under the MIT License.
#ifndef TOML11_EXCEPTION_HPP
#define TOML11_EXCEPTION_HPP
#include "source_location.hpp"
#include <stdexcept>
#include <string>
@ -11,15 +12,21 @@ namespace toml
struct exception : public std::exception
{
public:
exception(const source_location& loc): loc_(loc) {}
virtual ~exception() noexcept override = default;
virtual const char* what() const noexcept override {return "";}
virtual source_location const& location() const noexcept {return loc_;}
protected:
source_location loc_;
};
struct syntax_error : public toml::exception
{
public:
explicit syntax_error(const std::string& what_arg) : what_(what_arg){}
explicit syntax_error(const char* what_arg) : what_(what_arg){}
explicit syntax_error(const std::string& what_arg, const source_location& loc)
: exception(loc), what_(what_arg)
{}
virtual ~syntax_error() noexcept override = default;
virtual const char* what() const noexcept override {return what_.c_str();}
@ -30,8 +37,9 @@ struct syntax_error : public toml::exception
struct type_error : public toml::exception
{
public:
explicit type_error(const std::string& what_arg) : what_(what_arg){}
explicit type_error(const char* what_arg) : what_(what_arg){}
explicit type_error(const std::string& what_arg, const source_location& loc)
: exception(loc), what_(what_arg)
{}
virtual ~type_error() noexcept override = default;
virtual const char* what() const noexcept override {return what_.c_str();}
@ -42,10 +50,12 @@ struct type_error : public toml::exception
struct internal_error : public toml::exception
{
public:
explicit internal_error(const std::string& what_arg) : what_(what_arg){}
explicit internal_error(const char* what_arg) : what_(what_arg){}
explicit internal_error(const std::string& what_arg, const source_location& loc)
: exception(loc), what_(what_arg)
{}
virtual ~internal_error() noexcept override = default;
virtual const char* what() const noexcept override {return what_.c_str();}
protected:
std::string what_;
};

View File

@ -186,11 +186,11 @@ get(const basic_value<C, M, V>& v)
}
default:
{
throw type_error(detail::format_underline("[error] toml::value "
throw type_error(detail::format_underline("toml::value: "
"bad_cast to std::chrono::system_clock::time_point", {
{std::addressof(detail::get_region(v)),
concat_to_string("the actual type is ", v.type())}
}));
}), v.location());
}
}
}
@ -301,7 +301,7 @@ get(const basic_value<C, M, V>& v)
if(ar.size() != container.size())
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[erorr] toml::get specified container size is ", container.size(),
"toml::get: specified container size is ", container.size(),
" but there are ", ar.size(), " elements in toml array."), {
{std::addressof(detail::get_region(v)), "here"}
}));
@ -326,7 +326,7 @@ get(const basic_value<C, M, V>& v)
if(ar.size() != 2)
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[erorr] toml::get specified std::pair but there are ", ar.size(),
"toml::get: specified std::pair but there are ", ar.size(),
" elements in toml array."), {
{std::addressof(detail::get_region(v)), "here"}
}));
@ -357,8 +357,8 @@ get(const basic_value<C, M, V>& v)
if(ar.size() != std::tuple_size<T>::value)
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[erorr] toml::get specified std::tuple with ",
std::tuple_size<T>::value, "elements, but there are ", ar.size(),
"toml::get: specified std::tuple with ",
std::tuple_size<T>::value, " elements, but there are ", ar.size(),
" elements in toml array."), {
{std::addressof(detail::get_region(v)), "here"}
}));
@ -430,7 +430,7 @@ basic_value<C, M, V> const& find(const basic_value<C, M, V>& v, const key& ky)
if(tab.count(ky) == 0)
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[error] key \"", ky, "\" not found"), {
"key \"", ky, "\" not found"), {
{std::addressof(detail::get_region(v)), "in this table"}
}));
}
@ -444,7 +444,7 @@ basic_value<C, M, V>& find(basic_value<C, M, V>& v, const key& ky)
if(tab.count(ky) == 0)
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[error] key \"", ky, "\" not found"), {
"key \"", ky, "\" not found"), {
{std::addressof(detail::get_region(v)), "in this table"}
}));
}
@ -458,7 +458,7 @@ basic_value<C, M, V> find(basic_value<C, M, V>&& v, const key& ky)
if(tab.count(ky) == 0)
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[error] key \"", ky, "\" not found"), {
"key \"", ky, "\" not found"), {
{std::addressof(detail::get_region(v)), "in this table"}
}));
}
@ -523,7 +523,7 @@ find(const basic_value<C, M, V>& v, const key& ky)
if(tab.count(ky) == 0)
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[error] key \"", ky, "\" not found"), {
"key \"", ky, "\" not found"), {
{std::addressof(detail::get_region(v)), "in this table"}
}));
}
@ -539,7 +539,7 @@ find(basic_value<C, M, V>& v, const key& ky)
if(tab.count(ky) == 0)
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[error] key \"", ky, "\" not found"), {
"key \"", ky, "\" not found"), {
{std::addressof(detail::get_region(v)), "in this table"}
}));
}
@ -555,7 +555,7 @@ find(basic_value<C, M, V>&& v, const key& ky)
if(tab.count(ky) == 0)
{
throw std::out_of_range(detail::format_underline(concat_to_string(
"[error] key \"", ky, "\" not found"), {
"key \"", ky, "\" not found"), {
{std::addressof(detail::get_region(v)), "in this table"}
}));
}

View File

@ -76,7 +76,8 @@ inline ::toml::value operator"" _toml(const char* str, std::size_t len)
}
else // none of them.
{
throw ::toml::syntax_error(data.unwrap_err());
throw ::toml::syntax_error(data.unwrap_err(),
source_location(std::addressof(loc)));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
// Distributed under the MIT License.
#ifndef TOML11_REGION_HPP
#define TOML11_REGION_HPP
#include "exception.hpp"
#include <memory>
#include <vector>
#include <algorithm>
@ -10,6 +9,7 @@
#include <iterator>
#include <iomanip>
#include <cassert>
#include "color.hpp"
namespace toml
{
@ -231,11 +231,10 @@ struct region final : public region_base
region& operator+=(const region& other)
{
if(this->begin() != other.begin() || this->end() != other.end() ||
this->last_ != other.first_)
{
throw internal_error("invalid region concatenation");
}
// different regions cannot be concatenated
assert(this->begin() == other.begin() && this->end() == other.end() &&
this->last_ == other.first_);
this->last_ = other.last_;
return *this;
}
@ -420,7 +419,8 @@ struct region final : public region_base
// to show a better error message.
inline std::string format_underline(const std::string& message,
const std::vector<std::pair<region_base const*, std::string>>& reg_com,
const std::vector<std::string>& helps = {})
const std::vector<std::string>& helps = {},
const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED)
{
assert(!reg_com.empty());
@ -434,7 +434,27 @@ inline std::string format_underline(const std::string& message,
)->first->line_num().size());
std::ostringstream retval;
retval << message << '\n';
if(colorize)
{
retval << color::colorize; // turn on ANSI color
}
// XXX
// Here, before `colorize` support, it does not output `[error]` prefix
// automatically. So some user may output it manually and this change may
// duplicate the prefix. To avoid it, check the first 7 characters and
// if it is "[error]", it removes that part from the message shown.
if(message.size() > 7 && message.substr(0, 7) == "[error]")
{
retval << color::bold << color::red << "[error]" << color::reset
<< color::bold << message.substr(7) << color::reset << '\n';
}
else
{
retval << color::bold << color::red << "[error] " << color::reset
<< color::bold << message << color::reset << '\n';
}
for(auto iter = reg_com.begin(); iter != reg_com.end(); ++iter)
{
@ -442,34 +462,42 @@ inline std::string format_underline(const std::string& message,
if(iter != reg_com.begin() &&
std::prev(iter)->first->name() == iter->first->name())
{
retval << "\n ...\n";
retval << color::bold << color::blue << "\n ...\n" << color::reset;
}
else // if filename differs, print " --> filename.toml"
{
if(iter != reg_com.begin()) {retval << '\n';}
retval << " --> " << iter->first->name() << '\n';
retval << color::bold << color::blue << " --> " << color::reset
<< iter->first->name() << '\n';
// add one almost-empty line for readability
retval << make_string(static_cast<std::size_t>(line_num_width + 1), ' ')
<< color::bold << color::blue << " | " << color::reset << '\n';
}
const region_base* const reg = iter->first;
const std::string& comment = iter->second;
retval << ' ' << std::setw(line_num_width) << reg->line_num();
retval << " | " << reg->line() << '\n';
retval << make_string(static_cast<std::size_t>(line_num_width + 1), ' ');
retval << " | " << make_string(reg->before(), ' ');
retval << ' ' << color::bold << color::blue << std::setw(line_num_width)
<< std::right << reg->line_num() << " | " << color::reset
<< reg->line() << '\n';
retval << make_string(static_cast<std::size_t>(line_num_width + 1), ' ')
<< color::bold << color::blue << " | " << color::reset
<< make_string(reg->before(), ' ');
if(reg->size() == 1)
{
// invalid
// ^------
retval << '^';
retval << make_string(reg->after(), '-');
retval << color::bold << color::red
<< '^' << make_string(reg->after(), '-') << color::reset;
}
else
{
// invalid
// ~~~~~~~
const auto underline_len = std::min(reg->size(), reg->line().size());
retval << make_string(underline_len, '~');
retval << color::bold << color::red
<< make_string(underline_len, '~') << color::reset;
}
retval << ' ';
retval << comment;
@ -479,10 +507,10 @@ inline std::string format_underline(const std::string& message,
{
retval << '\n';
retval << make_string(static_cast<std::size_t>(line_num_width + 1), ' ');
retval << " | ";
retval << color::bold << color::blue << " | " << color::reset;
for(const auto help : helps)
{
retval << "\nHint: ";
retval << color::bold << "\nHint: " << color::reset;
retval << help;
}
}

View File

@ -118,8 +118,14 @@ struct serializer
{
return token; // there is no exponent part. just return it.
}
// zero-prefix in an exponent is NOT allowed in TOML.
#ifdef TOML11_USE_UNRELEASED_TOML_FEATURES
// Although currently it is not released yet, TOML will allow
// zero-prefix in an exponent part such as 1.234e+01.
// The following code removes the zero prefixes.
// If the feature is activated, the following codes can be skipped.
return token;
#endif
// zero-prefix in an exponent is NOT allowed in TOML v0.5.0.
// remove it if it exists.
bool sign_exists = false;
std::size_t zero_prefix = 0;

View File

@ -48,7 +48,11 @@ struct source_location
{
if(reg)
{
line_num_ = static_cast<std::uint_least32_t>(std::stoul(reg->line_num()));
if(reg->line_num() != detail::region_base().line_num())
{
line_num_ = static_cast<std::uint_least32_t>(
std::stoul(reg->line_num()));
}
column_num_ = static_cast<std::uint_least32_t>(reg->before() + 1);
region_size_ = static_cast<std::uint_least32_t>(reg->size());
file_name_ = reg->name();

View File

@ -2,6 +2,7 @@
// Distributed under the MIT License.
#ifndef TOML11_STRING_HPP
#define TOML11_STRING_HPP
#include <algorithm>
#include <string>
#include <cstdint>
#if __cplusplus >= 201703L
@ -45,6 +46,11 @@ struct string
operator std::string const& () const& noexcept {return str;}
operator std::string&& () && noexcept {return std::move(str);}
string& operator+=(const char* rhs) {str += rhs; return *this;}
string& operator+=(const char rhs) {str += rhs; return *this;}
string& operator+=(const std::string& rhs) {str += rhs; return *this;}
string& operator+=(const string& rhs) {str += rhs.str; return *this;}
#if __cplusplus >= 201703L
explicit string(std::string_view s): kind(string_t::basic), str(s){}
string(std::string_view s, string_t k): kind(k), str(s){}
@ -54,6 +60,8 @@ struct string
explicit operator std::string_view() const noexcept
{return std::string_view(str);}
string& operator+=(const std::string_view& rhs) {str += rhs; return *this;}
#endif
string_t kind;

View File

@ -105,7 +105,7 @@ struct has_into_toml_method
: decltype(has_into_toml_method_impl::check<T>(nullptr)){};
#ifdef __INTEL_COMPILER
#undef decltype(...)
#undef decltype
#endif
// ---------------------------------------------------------------------------

View File

@ -32,10 +32,10 @@ template<value_t Expected,
throw_bad_cast(value_t actual, const ::toml::basic_value<C, T, A>& v)
{
throw type_error(detail::format_underline(concat_to_string(
"[error] toml::value bad_cast to ", Expected), {
"toml::value: bad_cast to ", Expected), {
{std::addressof(get_region(v)),
concat_to_string("the actual type is ", actual)}
}));
}), v.location());
}
// switch by `value_t` and call the corresponding `value::as_xxx()`. {{{
@ -1579,6 +1579,14 @@ class basic_value
{
return this->as_table().at(k);
}
value_type& operator[](const key& k)
{
if(this->is_uninitialized())
{
*this = table_type{};
}
return this->as_table()[k];
}
value_type& at(const std::size_t idx)
{
@ -1589,6 +1597,15 @@ class basic_value
return this->as_array().at(idx);
}
value_type& operator[](const std::size_t idx) noexcept
{
return this->as_array(std::nothrow)[idx];
}
value_type const& operator[](const std::size_t idx) const noexcept
{
return this->as_array(std::nothrow)[idx];
}
source_location location() const
{
return source_location(this->region_info_.get());
@ -1843,25 +1860,27 @@ operator>=(const basic_value<C, T, A>& lhs, const basic_value<C, T, A>& rhs)
template<typename C, template<typename ...> class T, template<typename ...> class A>
inline std::string format_error(const std::string& err_msg,
const basic_value<C, T, A>& v, const std::string& comment,
std::vector<std::string> hints = {})
std::vector<std::string> hints = {},
const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED)
{
return detail::format_underline(err_msg,
std::vector<std::pair<detail::region_base const*, std::string>>{
{std::addressof(detail::get_region(v)), comment}
}, std::move(hints));
}, std::move(hints), colorize);
}
template<typename C, template<typename ...> class T, template<typename ...> class A>
inline std::string format_error(const std::string& err_msg,
const toml::basic_value<C, T, A>& v1, const std::string& comment1,
const toml::basic_value<C, T, A>& v2, const std::string& comment2,
std::vector<std::string> hints = {})
std::vector<std::string> hints = {},
const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED)
{
return detail::format_underline(err_msg,
std::vector<std::pair<detail::region_base const*, std::string>>{
{std::addressof(detail::get_region(v1)), comment1},
{std::addressof(detail::get_region(v2)), comment2}
}, std::move(hints));
}, std::move(hints), colorize);
}
template<typename C, template<typename ...> class T, template<typename ...> class A>
@ -1869,14 +1888,15 @@ inline std::string format_error(const std::string& err_msg,
const toml::basic_value<C, T, A>& v1, const std::string& comment1,
const toml::basic_value<C, T, A>& v2, const std::string& comment2,
const toml::basic_value<C, T, A>& v3, const std::string& comment3,
std::vector<std::string> hints = {})
std::vector<std::string> hints = {},
const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED)
{
return detail::format_underline(err_msg,
std::vector<std::pair<detail::region_base const*, std::string>>{
{std::addressof(detail::get_region(v1)), comment1},
{std::addressof(detail::get_region(v2)), comment2},
{std::addressof(detail::get_region(v3)), comment3}
}, std::move(hints));
}, std::move(hints), colorize);
}
template<typename Visitor, typename C,