From 42253150e43fa4289130d4b53729e618908a251e Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Thu, 15 Apr 2021 23:19:01 -0700 Subject: [PATCH] add toml11 as dependency --- CMakeLists.txt | 1 + COPYING.md | 24 + libraries/README.md | 7 + libraries/toml11/CMakeLists.txt | 5 + libraries/toml11/LICENSE | 21 + libraries/toml11/README.md | 1918 +++++++++++++++ libraries/toml11/include/toml.hpp | 46 + libraries/toml11/include/toml/color.hpp | 64 + libraries/toml11/include/toml/combinator.hpp | 306 +++ libraries/toml11/include/toml/comments.hpp | 466 ++++ libraries/toml11/include/toml/datetime.hpp | 631 +++++ libraries/toml11/include/toml/exception.hpp | 65 + libraries/toml11/include/toml/from.hpp | 20 + libraries/toml11/include/toml/get.hpp | 1068 ++++++++ libraries/toml11/include/toml/into.hpp | 20 + libraries/toml11/include/toml/lexer.hpp | 270 ++ libraries/toml11/include/toml/literal.hpp | 112 + libraries/toml11/include/toml/macros.hpp | 121 + libraries/toml11/include/toml/parser.hpp | 2177 +++++++++++++++++ libraries/toml11/include/toml/region.hpp | 417 ++++ libraries/toml11/include/toml/result.hpp | 717 ++++++ libraries/toml11/include/toml/serializer.hpp | 853 +++++++ .../toml11/include/toml/source_location.hpp | 233 ++ libraries/toml11/include/toml/storage.hpp | 43 + libraries/toml11/include/toml/string.hpp | 224 ++ libraries/toml11/include/toml/traits.hpp | 300 +++ libraries/toml11/include/toml/types.hpp | 150 ++ libraries/toml11/include/toml/utility.hpp | 93 + libraries/toml11/include/toml/value.hpp | 2023 +++++++++++++++ 29 files changed, 12395 insertions(+) create mode 100644 libraries/toml11/CMakeLists.txt create mode 100644 libraries/toml11/LICENSE create mode 100644 libraries/toml11/README.md create mode 100644 libraries/toml11/include/toml.hpp create mode 100644 libraries/toml11/include/toml/color.hpp create mode 100644 libraries/toml11/include/toml/combinator.hpp create mode 100644 libraries/toml11/include/toml/comments.hpp create mode 100644 libraries/toml11/include/toml/datetime.hpp create mode 100644 libraries/toml11/include/toml/exception.hpp create mode 100644 libraries/toml11/include/toml/from.hpp create mode 100644 libraries/toml11/include/toml/get.hpp create mode 100644 libraries/toml11/include/toml/into.hpp create mode 100644 libraries/toml11/include/toml/lexer.hpp create mode 100644 libraries/toml11/include/toml/literal.hpp create mode 100644 libraries/toml11/include/toml/macros.hpp create mode 100644 libraries/toml11/include/toml/parser.hpp create mode 100644 libraries/toml11/include/toml/region.hpp create mode 100644 libraries/toml11/include/toml/result.hpp create mode 100644 libraries/toml11/include/toml/serializer.hpp create mode 100644 libraries/toml11/include/toml/source_location.hpp create mode 100644 libraries/toml11/include/toml/storage.hpp create mode 100644 libraries/toml11/include/toml/string.hpp create mode 100644 libraries/toml11/include/toml/traits.hpp create mode 100644 libraries/toml11/include/toml/types.hpp create mode 100644 libraries/toml11/include/toml/utility.hpp create mode 100644 libraries/toml11/include/toml/value.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c93e7a9c..cfba34303 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,7 @@ add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/classparser) # google analytics library add_subdirectory(libraries/optional-bare) +add_subdirectory(libraries/toml11) # toml parser ############################### Built Artifacts ############################### diff --git a/COPYING.md b/COPYING.md index c0c986066..ea8fe479b 100644 --- a/COPYING.md +++ b/COPYING.md @@ -251,3 +251,27 @@ FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# toml11 + + The MIT License (MIT) + + Copyright (c) 2017 Toru Niina + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/libraries/README.md b/libraries/README.md index cdc720042..76ee8c461 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -158,3 +158,10 @@ A Google Analytics library for Qt. BSD licensed, derived from [qt-google-analytics](https://github.com/HSAnet/qt-google-analytics). Modifications include better handling of IP anonymization (can be enabled) and general improvements of the API (application handles persistence and ID generation instead of the library). + +## toml11 +A C++11 TOML language parser. Used by Forge 1.14+ to store mod metadata. + +See [github repo](https://github.com/ToruNiina/toml11). + +Licenced under the MIT licence. diff --git a/libraries/toml11/CMakeLists.txt b/libraries/toml11/CMakeLists.txt new file mode 100644 index 000000000..556ec1500 --- /dev/null +++ b/libraries/toml11/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.1) +project(toml11) + +add_library(toml11 INTERFACE) +target_include_directories(toml11 INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/libraries/toml11/LICENSE b/libraries/toml11/LICENSE new file mode 100644 index 000000000..f55c511d6 --- /dev/null +++ b/libraries/toml11/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Toru Niina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libraries/toml11/README.md b/libraries/toml11/README.md new file mode 100644 index 000000000..d5bc1c812 --- /dev/null +++ b/libraries/toml11/README.md @@ -0,0 +1,1918 @@ +toml11 +====== + +[![Build Status on GitHub Actions](https://github.com/ToruNiina/toml11/workflows/build/badge.svg)](https://github.com/ToruNiina/toml11/actions) +[![Build Status on TravisCI](https://travis-ci.org/ToruNiina/toml11.svg?branch=master)](https://travis-ci.org/ToruNiina/toml11) +[![Build status on Appveyor](https://ci.appveyor.com/api/projects/status/m2n08a926asvg5mg/branch/master?svg=true)](https://ci.appveyor.com/project/ToruNiina/toml11/branch/master) +[![Build status on CircleCI](https://circleci.com/gh/ToruNiina/toml11/tree/master.svg?style=svg)](https://circleci.com/gh/ToruNiina/toml11/tree/master) +[![Version](https://img.shields.io/github/release/ToruNiina/toml11.svg?style=flat)](https://github.com/ToruNiina/toml11/releases) +[![License](https://img.shields.io/github/license/ToruNiina/toml11.svg?style=flat)](LICENSE) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1209136.svg)](https://doi.org/10.5281/zenodo.1209136) + +toml11 is a C++11 (or later) header-only toml parser/encoder depending only on C++ standard library. + +- It is compatible to the latest version of [TOML v1.0.0](https://toml.io/en/v1.0.0). +- It is one of the most TOML standard compliant libraries, tested with [the language agnostic test suite for TOML parsers by BurntSushi](https://github.com/BurntSushi/toml-test). +- It shows highly informative error messages. You can see the error messages about invalid files at [CircleCI](https://circleci.com/gh/ToruNiina/toml11). +- It has configurable container. You can use any random-access containers and key-value maps as backend containers. +- It optionally preserves comments without any overhead. +- It has configurable serializer that supports comments, inline tables, literal strings and multiline strings. +- It supports user-defined type conversion from/into toml values. +- It correctly handles UTF-8 sequences, with or without BOM, both on posix and Windows. + +## Example + +```cpp +#include +#include + +int main() +{ + // ```toml + // title = "an example toml file" + // nums = [3, 1, 4, 1, 5] + // ``` + auto data = toml::parse("example.toml"); + + // find a value with the specified type from a table + std::string title = toml::find(data, "title"); + + // convert the whole array into any container automatically + std::vector nums = toml::find>(data, "nums"); + + // access with STL-like manner + if(not data.contains("foo")) + { + data["foo"] = "bar"; + } + + // pass a fallback + std::string name = toml::find_or(data, "name", "not found"); + + // width-dependent formatting + std::cout << std::setw(80) << data << std::endl; + + return 0; +} +``` + +## Table of Contents + +- [Integration](#integration) +- [Decoding a toml file](#decoding-a-toml-file) + - [In the case of syntax error](#in-the-case-of-syntax-error) + - [Invalid UTF-8 Codepoints](#invalid-utf-8-codepoints) +- [Finding a toml value](#finding-a-toml-value) + - [Finding a value in a table](#finding-a-value-in-a-table) + - [In case of error](#in-case-of-error) + - [Dotted keys](#dotted-keys) +- [Casting a toml value](#casting-a-toml-value) +- [Checking value type](#checking-value-type) +- [More about conversion](#more-about-conversion) + - [Converting an array](#converting-an-array) + - [Converting a table](#converting-a-table) + - [Getting an array of tables](#getting-an-array-of-tables) + - [Cost of conversion](#cost-of-conversion) + - [Converting datetime and its variants](#converting-datetime-and-its-variants) +- [Getting with a fallback](#getting-with-a-fallback) +- [Expecting conversion](#expecting-conversion) +- [Visiting a toml::value](#visiting-a-tomlvalue) +- [Constructing a toml::value](#constructing-a-tomlvalue) +- [Preserving Comments](#preserving-comments) +- [Customizing containers](#customizing-containers) +- [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) +- [Breaking Changes from v2](#breaking-changes-from-v2) +- [Running Tests](#running-tests) +- [Contributors](#contributors) +- [Licensing Terms](#licensing-terms) + +## Integration + +Just include the file after adding it to the include path. + +```cpp +#include // that's all! now you can use it. +#include + +int main() +{ + const auto data = toml::parse("example.toml"); + const auto title = toml::find(data, "title"); + std::cout << "the title is " << title << std::endl; + return 0; +} +``` + +The convenient way is to add this repository as a git-submodule or to install +it in your system by CMake. + +Note for MSVC: We recommend to set `/Zc:__cplusplus` to detect C++ version correctly. + +## Decoding a toml file + +To parse a toml file, the only thing you have to do is +to pass a filename to the `toml::parse` function. + +```cpp +const std::string fname("sample.toml"); +const toml::value data = toml::parse(fname); +``` + +As required by the TOML specification, the top-level value is always a table. +You can find a value inside it, cast it into a table explicitly, and insert it as a value into other `toml::value`. + +If it encounters an error while opening a file, it will throw `std::runtime_error`. + +You can also pass a `std::istream` to the `toml::parse` function. +To show a filename in an error message, however, it is recommended to pass the +filename with the stream. + +```cpp +std::ifstream ifs("sample.toml", std::ios_base::binary); +assert(ifs.good()); +const auto data = toml::parse(ifs, /*optional -> */ "sample.toml"); +``` + +**Note**: When you are **on Windows, open a file in binary mode**. +If a file is opened in text-mode, CRLF ("\r\n") will automatically be +converted to LF ("\n") and this causes inconsistency between file size +and the contents that would be read. This causes weird error. + +### In the case of syntax error + +If there is a syntax error in a toml file, `toml::parse` will throw +`toml::syntax_error` that inherits `std::exception`. + +toml11 has clean and informative error messages inspired by Rust and +it looks like the following. + +```console +terminate called after throwing an instance of 'toml::syntax_error' + what(): [error] toml::parse_table: invalid line format # error description + --> example.toml # file name + 3 | a = 42 = true # line num and content + | ^------ expected newline, but got '='. # error reason +``` + +If you (mistakenly) duplicate tables and got an error, it is helpful to see +where they are. toml11 shows both at the same time like the following. + +```console +terminate called after throwing an instance of 'toml::syntax_error' + what(): [error] toml::insert_value: table ("table") already exists. + --> duplicate-table.toml + 1 | [table] + | ~~~~~~~ table already exists here + ... + 3 | [table] + | ~~~~~~~ table defined twice +``` + +When toml11 encounters a malformed value, it tries to detect what type it is. +Then it shows hints to fix the format. An error message while reading one of +the malformed files in [the language agnostic test suite](https://github.com/BurntSushi/toml-test). +is shown below. + +```console +what(): [error] bad time: should be HH:MM:SS.subsec + --> ./datetime-malformed-no-secs.toml + 1 | no-secs = 1987-07-05T17:45Z + | ^------- HH:MM:SS.subsec + | +Hint: pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999 +Hint: fail: 1979-05-27T7:32:00, 1979-05-27 17:32 +``` + +You can find other examples in a job named `output_result` on +[CircleCI](https://circleci.com/gh/ToruNiina/toml11). + +Since the error message generation is generally a difficult task, the current +status is not ideal. If you encounter a weird error message, please let us know +and contribute to improve the quality! + +### Invalid UTF-8 codepoints + +It throws `syntax_error` if a value of an escape sequence +representing unicode character is not a valid UTF-8 codepoint. + +```console + what(): [error] toml::read_utf8_codepoint: input codepoint is too large. + --> utf8.toml + 1 | exceeds_unicode = "\U0011FFFF example" + | ^--------- should be in [0x00..0x10FFFF] +``` + +## Finding a toml value + +After parsing successfully, you can obtain the values from the result of +`toml::parse` using `toml::find` function. + +```toml +# sample.toml +answer = 42 +pi = 3.14 +numbers = [1,2,3] +time = 1979-05-27T07:32:00Z +``` + +``` cpp +const auto data = toml::parse("sample.toml"); +const auto answer = toml::find(data, "answer"); +const auto pi = toml::find(data, "pi"); +const auto numbers = toml::find>(data, "numbers"); +const auto timepoint = toml::find(data, "time"); +``` + +By default, `toml::find` returns a `toml::value`. + +```cpp +const toml::value& answer = toml::find(data, "answer"); +``` + +When you pass an exact TOML type that does not require type conversion, +`toml::find` returns a reference without copying the value. + +```cpp +const auto data = toml::parse("sample.toml"); +const auto& answer = toml::find(data, "answer"); +``` + +If the specified type requires conversion, you can't take a reference to the value. +See also [underlying types](#underlying-types). + +**NOTE**: For some technical reason, automatic conversion between `integer` and +`floating` is not supported. If you want to get a floating value even if a value +has integer value, you need to convert it manually after obtaining a value, +like the followings. + +```cpp +const auto vx = toml::find(data, "x"); +double x = vx.is_floating() ? vx.as_floating(std::nothrow) : + static_cast(vx.as_integer()); // it throws if vx is neither + // floating nor integer. +``` + +### Finding a value in a table + +There are several way to get a value defined in a table. +First, you can get a table as a normal value and find a value from the table. + +```toml +[fruit] +name = "apple" +[fruit.physical] +color = "red" +shape = "round" +``` + +``` cpp +const auto data = toml::parse("fruit.toml"); +const auto& fruit = toml::find(data, "fruit"); +const auto name = toml::find(fruit, "name"); + +const auto& physical = toml::find(fruit, "physical"); +const auto color = toml::find(physical, "color"); +const auto shape = toml::find(physical, "shape"); +``` + +Here, variable `fruit` is a `toml::value` and can be used as the first argument +of `toml::find`. + +Second, you can pass as many arguments as the number of subtables to `toml::find`. + +```cpp +const auto data = toml::parse("fruit.toml"); +const auto color = toml::find(data, "fruit", "physical", "color"); +const auto shape = toml::find(data, "fruit", "physical", "shape"); +``` + +### Finding a value in an array + +You can find n-th value in an array by `toml::find`. + +```toml +values = ["foo", "bar", "baz"] +``` + +``` cpp +const auto data = toml::parse("sample.toml"); +const auto values = toml::find(data, "values"); +const auto bar = toml::find(values, 1); +``` + +`toml::find` can also search array recursively. + +```cpp +const auto data = toml::parse("fruit.toml"); +const auto bar = toml::find(data, "values", 1); +``` + +Before calling `toml::find`, you can check if a value corresponding to a key +exists. You can use both `bool toml::value::contains(const key&) const` and +`std::size_t toml::value::count(const key&) const`. Those behaves like the +`std::map::contains` and `std::map::count`. + +```cpp +const auto data = toml::parse("fruit.toml"); +if(data.contains("fruit") && data.at("fruit").count("physical") != 0) +{ + // ... +} +``` + +### In case of error + +If the value does not exist, `toml::find` throws `std::out_of_range` with the +location of the table. + +```console +terminate called after throwing an instance of 'std::out_of_range' + what(): [error] key "answer" not found + --> example.toml + 6 | [tab] + | ~~~~~ in this table +``` + +---- + +If the specified type differs from the actual value contained, it throws +`toml::type_error` that inherits `std::exception`. + +Similar to the case of syntax error, toml11 also displays clean error messages. +The error message when you choose `int` to get `string` value would be like this. + +```console +terminate called after throwing an instance of 'toml::type_error' + what(): [error] toml::value bad_cast to integer + --> example.toml + 3 | title = "TOML Example" + | ~~~~~~~~~~~~~~ the actual type is string +``` + +**NOTE**: In order to show this kind of error message, all the toml values have +a pointer to represent its range in a file. The entire contents of a file is +shared by `toml::value`s and remains on the heap memory. It is recommended to +destruct all the `toml::value` classes after configuring your application +if you have a large TOML file compared to the memory resource. + +### Dotted keys + +TOML v0.5.0 has a new feature named "dotted keys". +You can chain keys to represent the structure of the data. + +```toml +physical.color = "orange" +physical.shape = "round" +``` + +This is equivalent to the following. + +```toml +[physical] +color = "orange" +shape = "round" +``` + +You can get both of the above tables with the same c++ code. + +```cpp +const auto physical = toml::find(data, "physical"); +const auto color = toml::find(physical, "color"); +``` + +The following code does not work for the above toml file. + +```cpp +// XXX this does not work! +const auto color = toml::find(data, "physical.color"); +``` + +The above code works with the following toml file. + +```toml +"physical.color" = "orange" +# equivalent to {"physical.color": "orange"}, +# NOT {"physical": {"color": "orange"}}. +``` + + +## Casting a toml value + +### `toml::get` + +`toml::parse` returns `toml::value`. `toml::value` is a union type that can +contain one of the following types. + +- `toml::boolean` (`bool`) +- `toml::integer` (`std::int64_t`) +- `toml::floating` (`double`) +- `toml::string` (a type convertible to std::string) +- `toml::local_date` +- `toml::local_time` +- `toml::local_datetime` +- `toml::offset_datetime` +- `toml::array` (by default, `std::vector`) + - It depends. See [customizing containers](#customizing-containers) for detail. +- `toml::table` (by default, `std::unordered_map`) + - It depends. See [customizing containers](#customizing-containers) for detail. + +To get a value inside, you can use `toml::get()`. The usage is the same as +`toml::find` (actually, `toml::find` internally uses `toml::get` after casting +a value to `toml::table`). + +``` cpp +const toml::value data = toml::parse("sample.toml"); +const toml::value answer_ = toml::get(data).at("answer"); +const std::int64_t answer = toml::get(answer_); +``` + +When you pass an exact TOML type that does not require type conversion, +`toml::get` returns a reference through which you can modify the content +(if the `toml::value` is `const`, it returns `const` reference). + +```cpp +toml::value data = toml::parse("sample.toml"); +toml::value answer_ = toml::get(data).at("answer"); +toml::integer& answer = toml::get(answer_); +answer = 6 * 9; // write to data.answer. now `answer_` contains 54. +``` + +If the specified type requires conversion, you can't take a reference to the value. +See also [underlying types](#underlying-types). + +It also throws a `toml::type_error` if the type differs. + +### `as_xxx` + +You can also use a member function to cast a value. + +```cpp +const std::int64_t answer = data.as_table().at("answer").as_integer(); +``` + +It also throws a `toml::type_error` if the type differs. If you are sure that +the value `v` contains a value of the specified type, you can suppress checking +by passing `std::nothrow`. + +```cpp +const auto& answer = data.as_table().at("answer"); +if(answer.is_integer() && answer.as_integer(std::nothrow) == 42) +{ + std::cout << "value is 42" << std::endl; +} +``` + +If `std::nothrow` is passed, the functions are marked as noexcept. + +By casting a `toml::value` into an array or a table, you can iterate over the +elements. + +```cpp +const auto data = toml::parse("example.toml"); +std::cout << "keys in the top-level table are the following: \n"; +for(const auto& [k, v] : data.as_table()) +{ + std::cout << k << '\n'; +} + +const auto& fruits = toml::find(data, "fruits"); +for(const auto& v : fruits.as_array()) +{ + std::cout << toml::find(v, "name") << '\n'; +} +``` + +The full list of the functions is below. + +```cpp +namespace toml { +class value { + // ... + const boolean& as_boolean() const&; + const integer& as_integer() const&; + const floating& as_floating() const&; + const string& as_string() const&; + const offset_datetime& as_offset_datetime() const&; + const local_datetime& as_local_datetime() const&; + const local_date& as_local_date() const&; + const local_time& as_local_time() const&; + const array& as_array() const&; + const table& as_table() const&; + // -------------------------------------------------------- + // non-const version + boolean& as_boolean() &; + // ditto... + // -------------------------------------------------------- + // rvalue version + boolean&& as_boolean() &&; + // ditto... + + // -------------------------------------------------------- + // noexcept versions ... + const boolean& as_boolean(const std::nothrow_t&) const& noexcept; + boolean& as_boolean(const std::nothrow_t&) & noexcept; + boolean&& as_boolean(const std::nothrow_t&) && noexcept; + // ditto... +}; +} // toml +``` + +### `at()` + +You can access to the element of a table and an array by `toml::basic_value::at`. + +```cpp +const toml::value v{1,2,3,4,5}; +std::cout << v.at(2).as_integer() << std::endl; // 3 + +const toml::value v{{"foo", 42}, {"bar", 3.14}}; +std::cout << v.at("foo").as_integer() << std::endl; // 42 +``` + +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. + +```cpp +const toml::value v = /* ... */; +if(v.is_integer()) +{ + std::cout << "value is an integer" << std::endl; +} +``` + +The complete list of the functions is below. + +```cpp +namespace toml { +class value { + // ... + bool is_boolean() const noexcept; + bool is_integer() const noexcept; + bool is_floating() const noexcept; + bool is_string() const noexcept; + bool is_offset_datetime() const noexcept; + bool is_local_datetime() const noexcept; + bool is_local_date() const noexcept; + bool is_local_time() const noexcept; + bool is_array() const noexcept; + bool is_table() const noexcept; + bool is_uninitialized() const noexcept; + // ... +}; +} // toml +``` + +Also, you can get `enum class value_t` from `toml::value::type()`. + +```cpp +switch(data.at("something").type()) +{ + case toml::value_t::integer: /*do some stuff*/ ; break; + case toml::value_t::floating: /*do some stuff*/ ; break; + case toml::value_t::string : /*do some stuff*/ ; break; + default : throw std::runtime_error( + "unexpected type : " + toml::stringize(data.at("something").type())); +} +``` + +The complete list of the `enum`s can be found in the section +[underlying types](#underlying-types). + +The `enum`s can be used as a parameter of `toml::value::is` function like the following. + +```cpp +toml::value v = /* ... */; +if(v.is(toml::value_t::boolean)) // ... +``` + +## More about conversion + +Since `toml::find` internally uses `toml::get`, all the following examples work +with both `toml::get` and `toml::find`. + +### Converting an array + +You can get any kind of `container` class from a `toml::array` +except for `map`-like classes. + +``` cpp +// # sample.toml +// numbers = [1,2,3] + +const auto numbers = toml::find(data, "numbers"); + +const auto vc = toml::get >(numbers); +const auto ls = toml::get >(numbers); +const auto dq = toml::get >(numbers); +const auto ar = toml::get>(numbers); +// if the size of data.at("numbers") is larger than that of std::array, +// it will throw toml::type_error because std::array is not resizable. +``` + +Surprisingly, you can convert `toml::array` into `std::pair` and `std::tuple`. + +```cpp +// numbers = [1,2,3] +const auto tp = toml::get>(numbers); +``` + +This functionality is helpful when you have a toml file like the following. + +```toml +array_of_arrays = [[1, 2, 3], ["foo", "bar", "baz"]] # toml allows this +``` + +What is the corresponding C++ type? +Obviously, it is a `std::pair` of `std::vector`s. + +```cpp +const auto array_of_arrays = toml::find(data, "array_of_arrays"); +const auto aofa = toml::get< + std::pair, std::vector> + >(array_of_arrays); +``` + +If you don't know the type of the elements, you can use `toml::array`, +which is a `std::vector` of `toml::value`, instead. + +```cpp +const auto a_of_a = toml::get(array_of_arrays); +const auto first = toml::get>(a_of_a.at(0)); +``` + +You can change the implementation of `toml::array` with `std::deque` or some +other array-like container. See [Customizing containers](#customizing-containers) +for detail. + +### Converting a table + +When all the values of the table have the same type, toml11 allows you to +convert a `toml::table` to a `map` that contains the convertible type. + +```toml +[tab] +key1 = "foo" # all the values are +key2 = "bar" # toml String +``` + +```cpp +const auto data = toml::parse("sample.toml"); +const auto tab = toml::find>(data, "tab"); +std::cout << tab["key1"] << std::endl; // foo +std::cout << tab["key2"] << std::endl; // bar +``` + +But since `toml::table` is just an alias of `std::unordered_map`, +normally you don't need to convert it because it has all the functionalities that +`std::unordered_map` has (e.g. `operator[]`, `count`, and `find`). In most cases +`toml::table` is sufficient. + +```cpp +toml::table tab = toml::get(data); +if(data.count("title") != 0) +{ + data["title"] = std::string("TOML example"); +} +``` + +You can change the implementation of `toml::table` with `std::map` or some +other map-like container. See [Customizing containers](#customizing-containers) +for detail. + +### Getting an array of tables + +An array of tables is just an array of tables. +You can get it in completely the same way as the other arrays and tables. + +```toml +# sample.toml +array_of_inline_tables = [{key = "value1"}, {key = "value2"}, {key = "value3"}] + +[[array_of_tables]] +key = "value4" +[[array_of_tables]] +key = "value5" +[[array_of_tables]] +key = "value6" +``` + +```cpp +const auto data = toml::parse("sample.toml"); +const auto aot1 = toml::find>(data, "array_of_inline_tables"); +const auto aot2 = toml::find>(data, "array_of_tables"); +``` + +### Cost of conversion + +Although conversion through `toml::(get|find)` is convenient, it has additional +copy-cost because it copies data contained in `toml::value` to the +user-specified type. Of course in some cases this overhead is not ignorable. + +```cpp +// the following code constructs a std::vector. +// it requires heap allocation for vector and element conversion. +const auto array = toml::find>(data, "foo"); +``` + +By passing the exact types, `toml::get` returns reference that has no overhead. + +``` cpp +const auto& tab = toml::find(data, "tab"); +const auto& numbers = toml::find(data, "numbers"); +``` + +Also, `as_xxx` are zero-overhead because they always return a reference. + +``` cpp +const auto& tab = toml::find(data, "tab" ).as_table(); +const auto& numbers = toml::find(data, "numbers").as_array(); +``` + +In this case you need to call `toml::get` each time you access to +the element of `toml::array` because `toml::array` is an array of `toml::value`. + +```cpp +const auto& num0 = toml::get(numbers.at(0)); +const auto& num1 = toml::get(numbers.at(1)); +const auto& num2 = toml::get(numbers.at(2)); +``` + +### Converting datetime and its variants + +TOML v0.5.0 has 4 different datetime objects, `local_date`, `local_time`, +`local_datetime`, and `offset_datetime`. + +Since `local_date`, `local_datetime`, and `offset_datetime` represent a time +point, you can convert them to `std::chrono::system_clock::time_point`. + +Contrary, `local_time` does not represents a time point because they lack a +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 +o_dt = 2018-12-23T12:30:00+09:30 +``` + +```cpp +const auto data = toml::parse("sample.toml"); + +const auto date = toml::get(data.at("date")); +const auto l_dt = toml::get(data.at("l_dt")); +const auto o_dt = toml::get(data.at("o_dt")); + +const auto time = toml::get(data.at("time")); // 12 * 60 + 30 min +``` + +`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 + +`toml::find_or` returns a default value if the value is not found or has a +different type. + +```cpp +const auto data = toml::parse("example.toml"); +const auto num = toml::find_or(data, "num", 42); +``` + +Also, `toml::get_or` returns a default value if `toml::get` failed. + +```cpp +toml::value v("foo"); // v contains String +const int value = toml::get_or(v, 42); // conversion fails. it returns 42. +``` + +These functions automatically deduce what type you want to get +from the default value you passed. + +To get a reference through this function, take care about the default value. + +```cpp +toml::value v("foo"); // v contains String +toml::integer& i = toml::get_or(v, 42); // does not work because binding `42` + // to `integer&` is invalid +toml::integer opt = 42; +toml::integer& i = toml::get_or(v, opt); // this works. +``` + +## Expecting conversion + +By using `toml::expect`, you will get your expected value or an error message +without throwing `toml::type_error`. + +```cpp +const auto value = toml::expect(data.at("title")); +if(value.is_ok()) { + std::cout << value.unwrap() << std::endl; +} else { + std::cout << value.unwrap_err() << std::endl; +} +``` + +Also, you can pass a function object to modify the expected value. + +```cpp +const auto value = toml::expect(data.at("number")) + .map(// function that receives expected type (here, int) + [](const int number) -> double { + return number * 1.5 + 1.0; + }).unwrap_or(/*default value =*/ 3.14); +``` + +## Visiting a toml::value + +toml11 provides `toml::visit` to apply a function to `toml::value` in the +same way as `std::variant`. + +```cpp +const toml::value v(3.14); +toml::visit([](const auto& val) -> void { + std::cout << val << std::endl; + }, v); +``` + +The function object that would be passed to `toml::visit` must be able to +recieve all the possible TOML types. Also, the result types should be the same +each other. + +## Constructing a toml::value + +`toml::value` can be constructed in various ways. + +```cpp +toml::value v(true); // boolean +toml::value v(42); // integer +toml::value v(3.14); // floating +toml::value v("foobar"); // string +toml::value v(toml::local_date(2019, toml::month_t::Apr, 1)); // date +toml::value v{1, 2, 3, 4, 5}; // array +toml::value v{{"foo", 42}, {"bar", 3.14}, {"baz", "qux"}}; // table +``` + +When constructing a string, you can choose to use either literal or basic string. +By default, it will be a basic string. + +```cpp +toml::value v("foobar", toml::string_t::basic ); +toml::value v("foobar", toml::string_t::literal); +``` + +Datetime objects can be constructed from `std::tm` and +`std::chrono::system_clock::time_point`. But you need to specify what type +you use to avoid ambiguity. + +```cpp +const auto now = std::chrono::system_clock::now(); +toml::value v(toml::local_date(now)); +toml::value v(toml::local_datetime(now)); +toml::value v(toml::offset_datetime(now)); +``` + +Since local time is not equivalent to a time point, because it lacks date +information, it will be constructed from `std::chrono::duration`. + +```cpp +toml::value v(toml::local_time(std::chrono::hours(10))); +``` + +You can construct an array object not only from `initializer_list`, but also +from STL containers. In that case, the element type must be convertible to +`toml::value`. + +```cpp +std::vector vec{1,2,3,4,5}; +toml::value v(vec); +``` + +When you construct an array value, all the elements of `initializer_list` +must be convertible into `toml::value`. + +If a `toml::value` has an array, you can `push_back` an element in it. + +```cpp +toml::value v{1,2,3,4,5}; +v.push_back(6); +``` + +`emplace_back` also works. + +## Preserving comments + +After toml11 v3, you can choose whether comments are preserved or not. + +```cpp +const auto data1 = toml::parse("example.toml"); +const auto data2 = toml::parse("example.toml"); +``` + +Comments related to a value can be obtained by `toml::value::comments()`. +The return value has the same interface as `std::vector`. + +```cpp +const auto& com = v.comments(); +for(const auto& c : com) +{ + std::cout << c << std::endl; +} +``` + +Comments just before and just after (within the same line) a value are kept in a value. + +```toml +# this is a comment for v1. +v1 = "foo" + +v2 = "bar" # this is a comment for v2. +# Note that this comment is NOT a comment for v2. + +# this comment is not related to any value +# because there are empty lines between v3. +# this comment will be ignored even if you set `preserve_comments`. + +# this is a comment for v3 +# this is also a comment for v3. +v3 = "baz" # ditto. +``` + +Each comment line becomes one element of a `std::vector`. + +Hash signs will be removed, but spaces after hash sign will not be removed. + +```cpp +v1.comments().at(0) == " this is a comment for v1."s; + +v2.comments().at(1) == " this is a comment for v1."s; + +v3.comments().at(0) == " this is a comment for v3."s; +v3.comments().at(1) == " this is also a comment for v3."s; +v3.comments().at(2) == " ditto."s; +``` + +Note that a comment just after an opening brace of an array will not be a +comment for the array. + +```toml +# this is a comment for a. +a = [ # this is not a comment for a. this will be ignored. + 1, 2, 3, + # this is a comment for `42`. + 42, # this is also a comment for `42`. + 5 +] # this is a comment for a. +``` + +You can also append and modify comments. +The interfaces are the same as `std::vector`. + +```cpp +toml::basic_value v(42); +v.comments().push_back(" add this comment."); +// # add this comment. +// i = 42 +``` + +Also, you can pass a `std::vector` when constructing a +`toml::basic_value`. + +```cpp +std::vector comments{"comment 1", "comment 2"}; +const toml::basic_value v1(42, std::move(comments)); +const toml::basic_value v2(42, {"comment 1", "comment 2"}); +``` + +When `toml::discard_comments` is chosen, comments will not be contained in a value. +`value::comments()` will always be kept empty. +All the modification on comments would be ignored. +All the element access in a `discard_comments` causes the same error as accessing +an element of an empty `std::vector`. + +The comments will also be serialized. If comments exist, those comments will be +added just before the values. + +__NOTE__: Result types from `toml::parse(...)` and +`toml::parse(...)` are different. + +## Customizing containers + +Actually, `toml::basic_value` has 3 template arguments. + +```cpp +template class Table = std::unordered_map, + template class Array = std::vector> +class basic_value; +``` + +This enables you to change the containers used inside. E.g. you can use +`std::map` to contain a table object instead of `std::unordered_map`. +And also can use `std::deque` as a array object instead of `std::vector`. + +You can set these parameters while calling `toml::parse` function. + +```cpp +const auto data = toml::parse< + toml::preserve_comments, std::map, std::deque + >("example.toml"); +``` + +Needless to say, the result types from `toml::parse(...)` and +`toml::parse(...)` are different (unless you specify the same +types as default). + +Note that, since `toml::table` and `toml::array` is an alias for a table and an +array of a default `toml::value`, so it is different from the types actually +contained in a `toml::basic_value` when you customize containers. +To get the actual type in a generic way, use +`typename toml::basic_type::table_type` and +`typename toml::basic_type::array_type`. + +## TOML literal + +toml11 supports `"..."_toml` literal. +It accept both a bare value and a file content. + +```cpp +using namespace toml::literals::toml_literals; + +// `_toml` can convert a bare value without key +const toml::value v = u8"0xDEADBEEF"_toml; +// v is an Integer value containing 0xDEADBEEF. + +// raw string literal (`R"(...)"` is useful for this purpose) +const toml::value t = u8R"( + title = "this is TOML literal" + [table] + key = "value" +)"_toml; +// the literal will be parsed and the result will be contained in t +``` + +The literal function is defined in the same way as the standard library literals +such as `std::literals::string_literals::operator""s`. + +```cpp +namespace toml +{ +inline namespace literals +{ +inline namespace toml_literals +{ +toml::value operator"" _toml(const char* str, std::size_t len); +} // toml_literals +} // literals +} // toml +``` + +Access to the operator can be gained with `using namespace toml::literals;`, +`using namespace toml::toml_literals`, and `using namespace toml::literals::toml_literals`. + +Note that a key that is composed only of digits is allowed in TOML. +And, unlike the file parser, toml-literal allows a bare value without a key. +Thus it is difficult to distinguish arrays having integers and definitions of +tables that are named as digits. +Currently, literal `[1]` becomes a table named "1". +To ensure a literal to be considered as an array with one element, you need to +add a comma after the first element (like `[1,]`). + +```cpp +"[1,2,3]"_toml; // This is an array +"[table]"_toml; // This is a table that has an empty table named "table" inside. +"[[1,2,3]]"_toml; // This is an array of arrays +"[[table]]"_toml; // This is a table that has an array of tables inside. + +"[[1]]"_toml; // This literal is ambiguous. + // Currently, it becomes a table that has array of table "1". +"1 = [{}]"_toml; // This is a table that has an array of table named 1. +"[[1,]]"_toml; // This is an array of arrays. +"[[1],]"_toml; // ditto. +``` + +NOTE: `_toml` literal returns a `toml::value` that does not have comments. + +## Conversion between toml value and arbitrary types + +You can also use `toml::get` and other related functions with the types +you defined after you implement a way to convert it. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; +}; +} // ext + +const auto data = toml::parse("example.toml"); + +// to do this +const foo f = toml::find(data, "foo"); +``` + +There are 3 ways to use `toml::get` with the types that you defined. + +The first one is to implement `from_toml(const toml::value&)` member function. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; + + void from_toml(const toml::value& v) + { + this->a = toml::find(v, "a"); + this->b = toml::find(v, "b"); + this->c = toml::find(v, "c"); + return; + } +}; +} // ext +``` + +In this way, because `toml::get` first constructs `foo` without arguments, +the type should be default-constructible. + +The second is to implement `constructor(const toml::value&)`. + +```cpp +namespace ext +{ +struct foo +{ + explicit foo(const toml::value& v) + : a(toml::find(v, "a")), b(toml::find(v, "b")), + c(toml::find(v, "c")) + {} + + int a; + double b; + std::string c; +}; +} // ext +``` + +Note that implicit default constructor declaration will be suppressed +when a constructor is defined. If you want to use the struct (here, `foo`) +in a container (e.g. `std::vector`), you may need to define default +constructor explicitly. + +The third is to implement specialization of `toml::from` for your type. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; +}; +} // ext + +namespace toml +{ +template<> +struct from +{ + static ext::foo from_toml(const value& v) + { + ext::foo f; + f.a = find(v, "a"); + f.b = find(v, "b"); + f.c = find(v, "c"); + return f; + } +}; +} // toml +``` + +In this way, since the conversion function is defined outside of the class, +you can add conversion between `toml::value` and classes defined in another library. + +Note that you cannot implement both of the functions because the overload +resolution of `toml::get` will be ambiguous. + +If you want to convert any versions of `toml::basic_value`, +you need to templatize the conversion function as follows. + +```cpp +struct foo +{ + template class M, template class A> + void from_toml(const toml::basic_value& v) + { + this->a = toml::find(v, "a"); + this->b = toml::find(v, "b"); + this->c = toml::find(v, "c"); + return; + } +}; +// or +namespace toml +{ +template<> +struct from +{ + template class M, template class A> + static ext::foo from_toml(const basic_value& v) + { + ext::foo f; + f.a = find(v, "a"); + f.b = find(v, "b"); + f.c = find(v, "c"); + return f; + } +}; +} // toml +``` + +---- + +The opposite direction is also supported in a similar way. You can directly +pass your type to `toml::value`'s constructor by introducing `into_toml` or +`toml::into`. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; + + toml::value into_toml() const // you need to mark it const. + { + return toml::value{{"a", this->a}, {"b", this->b}, {"c", this->c}}; + } +}; +} // ext + +ext::foo f{42, 3.14, "foobar"}; +toml::value v(f); +``` + +The definition of `toml::into` is similar to `toml::from`. + +```cpp +namespace ext +{ +struct foo +{ + int a; + double b; + std::string c; +}; +} // ext + +namespace toml +{ +template<> +struct into +{ + static toml::value into_toml(const ext::foo& f) + { + return toml::value{{"a", f.a}, {"b", f.b}, {"c", f.c}}; + } +}; +} // toml + +ext::foo f{42, 3.14, "foobar"}; +toml::value v(f); +``` + +Any type that can be converted to `toml::value`, e.g. `int`, `toml::table` and +`toml::array` are okay to return from `into_toml`. + +You can also return a custom `toml::basic_value` from `toml::into`. + +```cpp +namespace toml +{ +template<> +struct into +{ + static toml::basic_value into_toml(const ext::foo& f) + { + toml::basic_value v{{"a", f.a}, {"b", f.b}, {"c", f.c}}; + v.comments().push_back(" comment"); + return v; + } +}; +} // toml +``` + +But note that, if this `basic_value` would be assigned into other `toml::value` +that discards `comments`, the comments would be dropped. + +### Macro to automatically define conversion functions + +There is a helper macro that automatically generates conversion functions `from` and `into` for a simple struct. + +```cpp +namespace foo +{ +struct Foo +{ + std::string s; + double d; + int i; +}; +} // foo + +TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i) + +int main() +{ + const auto file = toml::parse("example.toml"); + auto f = toml::find(file, "foo"); +} +``` + +And then you can use `toml::find(file, "foo");` + +**Note** that, because of a slight difference in implementation of preprocessor between gcc/clang and MSVC, [you need to define `/Zc:preprocessor`](https://github.com/ToruNiina/toml11/issues/139#issuecomment-803683682) to use it in MSVC (Thank you @glebm !). + +## Formatting user-defined error messages + +When you encounter an error after you read the toml value, you may want to +show the error with the value. + +toml11 provides you a function that formats user-defined error message with +related values. With a code like the following, + +```cpp +const auto value = toml::find(data, "num"); +if(value < 0) +{ + std::cerr << toml::format_error("[error] value should be positive", + data.at("num"), "positive number required") + << std::endl; +} +``` + +you will get an error message like this. + +```console +[error] value should be positive + --> example.toml + 3 | num = -42 + | ~~~ positive number required +``` + +When you pass two values to `toml::format_error`, + +```cpp +const auto min = toml::find(range, "min"); +const auto max = toml::find(range, "max"); +if(max < min) +{ + std::cerr << toml::format_error("[error] max should be larger than min", + data.at("min"), "minimum number here", + data.at("max"), "maximum number here"); + << std::endl; +} +``` + +you will get an error message like this. + +```console +[error] max should be larger than min + --> example.toml + 3 | min = 54 + | ~~ minimum number here + ... + 4 | max = 42 + | ~~ maximum number here +``` + +You can print hints at the end of the message. + +```cpp +std::vector 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`. + +```cpp +struct source_location +{ + std::uint_least32_t line() const noexcept; + std::uint_least32_t column() const noexcept; + std::uint_least32_t region() const noexcept; + std::string const& file_name() const noexcept; + std::string const& line_str() const noexcept; +}; +// +-- line() +--- length of the region (here, region() == 9) +// v .---+---. +// 12 | value = "foo bar" <- line_str() returns the line itself. +// ^-------- column() points here +``` + +You can get this by +```cpp +const toml::value v = /*...*/; +const toml::source_location loc = v.location(); +``` + +## Exceptions + +The following `exception` classes 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. + +`syntax_error` will be thrown from `toml::parse` and `_toml` literal. +`type_error` will be thrown from `toml::get/find`, `toml::value::as_xxx()`, and +other functions that takes a content inside of `toml::value`. + +Note that, currently, from `toml::value::at()` and `toml::find(value, key)` +may throw an `std::out_of_range` that does not inherits `toml::exception`. + +Also, in some cases, most likely in the file open error, it will throw an +`std::runtime_error`. + +## 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. + +```cpp +const toml::value data{{"foo", 42}, {"bar", "baz"}}; +std::cout << data << std::endl; +// bar = "baz" +// foo = 42 +``` + +toml11 automatically makes a small table and small array inline. +You can specify the width to make them inline by `std::setw` for streams. + +```cpp +const toml::value data{ + {"qux", {{"foo", 42}, {"bar", "baz"}}}, + {"quux", {"small", "array", "of", "strings"}}, + {"foobar", {"this", "array", "of", "strings", "is", "too", "long", + "to", "print", "into", "single", "line", "isn't", "it?"}}, +}; + +// the threshold becomes 80. +std::cout << std::setw(80) << data << std::endl; +// foobar = [ +// "this","array","of","strings","is","too","long","to","print","into", +// "single","line","isn't","it?", +// ] +// quux = ["small","array","of","strings"] +// qux = {bar="baz",foo=42} + + +// the width is 0. nothing become inline. +std::cout << std::setw(0) << data << std::endl; +// foobar = [ +// "this", +// ... (snip) +// "it?", +// ] +// quux = [ +// "small", +// "array", +// "of", +// "strings", +// ] +// [qux] +// bar = "baz" +// foo = 42 +``` + +It is recommended to set width before printing data. Some I/O functions changes +width to 0, and it makes all the stuff (including `toml::array`) multiline. +The resulting files becomes too long. + +To control the precision of floating point numbers, you need to pass +`std::setprecision` to stream. + +```cpp +const toml::value data{ + {"pi", 3.141592653589793}, + {"e", 2.718281828459045} +}; +std::cout << std::setprecision(17) << data << std::endl; +// e = 2.7182818284590451 +// pi = 3.1415926535897931 +std::cout << std::setprecision( 7) << data << std::endl; +// e = 2.718282 +// pi = 3.141593 +``` + +There is another way to format toml values, `toml::format()`. +It returns `std::string` that represents a value. + +```cpp +const toml::value v{{"a", 42}}; +const std::string fmt = toml::format(v); +// a = 42 +``` + +Note that since `toml::format` formats a value, the resulting string may lack +the key value. + +```cpp +const toml::value v{3.14}; +const std::string fmt = toml::format(v); +// 3.14 +``` + +To control the width and precision, `toml::format` receives optional second and +third arguments to set them. By default, the witdh is 80 and the precision is +`std::numeric_limits::max_digit10`. + +```cpp +const auto serial = toml::format(data, /*width = */ 0, /*prec = */ 17); +``` + +When you pass a comment-preserving-value, the comment will also be serialized. +An array or a table containing a value that has a comment would not be inlined. + +## Underlying types + +The toml types (can be used as `toml::*` in this library) and corresponding `enum` names are listed in the table below. + +| TOML type | underlying c++ type | enum class | +| -------------- | ---------------------------------- | -------------------------------- | +| Boolean | `bool` | `toml::value_t::boolean` | +| Integer | `std::int64_t` | `toml::value_t::integer` | +| Float | `double` | `toml::value_t::floating` | +| String | `toml::string` | `toml::value_t::string` | +| LocalDate | `toml::local_date` | `toml::value_t::local_date` | +| LocalTime | `toml::local_time` | `toml::value_t::local_time` | +| LocalDatetime | `toml::local_datetime` | `toml::value_t::local_datetime` | +| OffsetDatetime | `toml::offset_datetime` | `toml::value_t::offset_datetime` | +| Array | `array-like` | `toml::value_t::array` | +| Table | `map-like` | `toml::value_t::table` | + +`array-like` and `map-like` are the STL containers that works like a `std::vector` and +`std::unordered_map`, respectively. By default, `std::vector` and `std::unordered_map` +are used. See [Customizing containers](#customizing-containers) for detail. + +`toml::string` is effectively the same as `std::string` but has an additional +flag that represents a kind of a string, `string_t::basic` and `string_t::literal`. +Although `std::string` is not an exact toml type, still you can get a reference +that points to internal `std::string` by using `toml::get()` for convenience. +The most important difference between `std::string` and `toml::string` is that +`toml::string` will be formatted as a TOML string when outputed with `ostream`. +This feature is introduced to make it easy to write a custom serializer. + +`Datetime` variants are `struct` that are defined in this library. +Because `std::chrono::system_clock::time_point` is a __time point__, +not capable of representing a Local Time independent from a specific day. + +## Unreleased TOML features + +Since TOML v1.0.0-rc.1 has been released, those features are now activated by +default. We no longer need to define `TOML11_USE_UNRELEASED_FEATURES`. + +- 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. Here the reason is explained. + +```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. By explicitly converting values into +`toml::value`, you can initialize `toml::value` with a heterogeneous array. +Also, you can first initialize a `toml::value` with an array and then +`push_back` into it. + +```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"}} +} + +// OK! +toml::value v(toml::array{}); +v.push_back("foo"); +v.push_back(3.14); + +// OK! +toml::array a; +a.push_back("foo"); +a.push_back(3.14); +toml::value v(std::move(a)); +``` + +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, we need to explicitly cast values into +`toml::value` when you initialize heterogeneous array in a C++ code. + +```cpp +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 + +Although toml11 is relatively new library (it's three years old now), it had +some confusing and inconvenient user-interfaces because of historical reasons. + +Between v2 and v3, those interfaces are rearranged. + +- `toml::parse` now returns a `toml::value`, not `toml::table`. +- `toml::value` is now an alias of `toml::basic_value`. + - See [Customizing containers](#customizing-containers) for detail. +- The elements of `toml::value_t` are renamed as `snake_case`. + - See [Underlying types](#underlying-types) for detail. +- Supports for the CamelCaseNames are dropped. + - See [Underlying types](#underlying-types) for detail. +- `(is|as)_float` has been removed to make the function names consistent with others. + - Since `float` is a keyword, toml11 named a float type as `toml::floating`. + - Also a `value_t` corresponds to `toml::floating` is named `value_t::floating`. + - So `(is|as)_floating` is introduced and `is_float` has been removed. + - See [Casting a toml::value](#casting-a-tomlvalue) and [Checking value type](#checking-value-type) for detail. +- An overload of `toml::find` for `toml::table` has been dropped. Use `toml::value` version instead. + - Because type conversion between a table and a value causes ambiguity while overload resolution + - Since `toml::parse` now returns a `toml::value`, this feature becomes less important. + - Also because `toml::table` is a normal STL container, implementing utility function is easy. + - See [Finding a toml::value](#finding-a-toml-value) for detail. +- An overload of `operator<<` and `toml::format` for `toml::table`s are dropped. + - Use `toml::value` instead. + - See [Serializing TOML data](#serializing-toml-data) for detail. +- Interface around comments. + - See [Preserving Comments](#preserving-comments) for detail. +- An ancient `from_toml/into_toml` has been removed. Use arbitrary type conversion support. + - See [Conversion between toml value and arbitrary types](#conversion-between-toml-value-and-arbitrary-types) for detail. + +Such a big change will not happen in the coming years. + +## Running Tests + +After cloning this repository, run the following command (thank you @jwillikers +for automating test set fetching!). + +```sh +$ mkdir build +$ cd build +$ cmake .. -Dtoml11_BUILD_TEST=ON +$ make +$ make test +``` + +To run the language agnostic test suite, you need to compile +`tests/check_toml_test.cpp` and pass it to the tester. + +## Contributors + +I appreciate the help of the contributors who introduced the great feature to this library. + +- Guillaume Fraux (@Luthaf) + - Windows support and CI on Appvayor + - Intel Compiler support +- Quentin Khan (@xaxousis) + - Found & Fixed a bug around ODR + - Improved error messages for invaild keys to show the location where the parser fails +- Petr Beneš (@wbenny) + - Fixed warnings on MSVC +- Ivan Shynkarenka (@chronoxor) + - Fixed Visual Studio 2019 warnings +- @khoitd1997 + - Fixed warnings while type conversion +- @KerstinKeller + - 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 +- Jordan Williams (@jwillikers) + - Fixed clang range-loop-analysis warnings + - Fixed feature test macro to suppress -Wundef + - Use cache variables in CMakeLists.txt + - Automate test set fetching, update and refactor CMakeLists.txt +- Scott McCaskill + - Parse 9 digits (nanoseconds) of fractional seconds in a `local_time` +- Shu Wang (@halfelf) + - fix "Finding a value in an array" example in README +- @maass-tv and @SeverinLeonhardt + - Fix MSVC warning C4866 +- OGAWA KenIchi (@kenichiice) + - Fix include path in README +- Mohammed Alyousef (@MoAlyousef) + - Made testing optional in CMake +- Ivan Shynkarenka (@chronoxor) + - Fix compilation error in `` with MinGW +- Alex Merry (@amerry) + - Add missing include files +- sneakypete81 (@sneakypete81) + - Fix typo in error message + + +## Licensing terms + +This product is licensed under the terms of the [MIT License](LICENSE). + +- Copyright (c) 2017-2020 Toru Niina + +All rights reserved. diff --git a/libraries/toml11/include/toml.hpp b/libraries/toml11/include/toml.hpp new file mode 100644 index 000000000..9ed1216e7 --- /dev/null +++ b/libraries/toml11/include/toml.hpp @@ -0,0 +1,46 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Toru Niina + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TOML_FOR_MODERN_CPP +#define TOML_FOR_MODERN_CPP + +#ifndef __cplusplus +# error "__cplusplus is not defined" +#endif + +#if __cplusplus < 201103L && _MSC_VER < 1900 +# error "toml11 requires C++11 or later." +#endif + +#define TOML11_VERSION_MAJOR 3 +#define TOML11_VERSION_MINOR 6 +#define TOML11_VERSION_PATCH 1 + +#include "toml/parser.hpp" +#include "toml/literal.hpp" +#include "toml/serializer.hpp" +#include "toml/get.hpp" +#include "toml/macros.hpp" + +#endif// TOML_FOR_MODERN_CPP diff --git a/libraries/toml11/include/toml/color.hpp b/libraries/toml11/include/toml/color.hpp new file mode 100644 index 000000000..4cb572cb0 --- /dev/null +++ b/libraries/toml11/include/toml/color.hpp @@ -0,0 +1,64 @@ +#ifndef TOML11_COLOR_HPP +#define TOML11_COLOR_HPP +#include +#include + +#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 diff --git a/libraries/toml11/include/toml/combinator.hpp b/libraries/toml11/include/toml/combinator.hpp new file mode 100644 index 000000000..e250188fe --- /dev/null +++ b/libraries/toml11/include/toml/combinator.hpp @@ -0,0 +1,306 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_COMBINATOR_HPP +#define TOML11_COMBINATOR_HPP +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "region.hpp" +#include "result.hpp" +#include "traits.hpp" +#include "utility.hpp" + +// they scans characters and returns region if it matches to the condition. +// when they fail, it does not change the location. +// in lexer.hpp, these are used. + +namespace toml +{ +namespace detail +{ + +// to output character as an error message. +inline std::string show_char(const char c) +{ + // It supress an error that occurs only in Debug mode of MSVC++ on Windows. + // I'm not completely sure but they check the value of char to be in the + // range [0, 256) and some of the COMPLETELY VALID utf-8 character sometimes + // has negative value (if char has sign). So here it re-interprets c as + // unsigned char through pointer. In general, converting pointer to a + // pointer that has different type cause UB, but `(signed|unsigned)?char` + // are one of the exceptions. Converting pointer only to char and std::byte + // (c++17) are valid. + if(std::isgraph(*reinterpret_cast(std::addressof(c)))) + { + return std::string(1, c); + } + else + { + std::array buf; + buf.fill('\0'); + const auto r = std::snprintf( + buf.data(), buf.size(), "0x%02x", static_cast(c) & 0xFF); + (void) r; // Unused variable warning + assert(r == static_cast(buf.size()) - 1); + return std::string(buf.data()); + } +} + +template +struct character +{ + static constexpr char target = C; + + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + const auto first = loc.iter(); + + const char c = *(loc.iter()); + if(c != target) + { + return none(); + } + loc.advance(); // update location + + return ok(region(loc, first, loc.iter())); + } +}; +template +constexpr char character::target; + +// closed interval [Low, Up]. both Low and Up are included. +template +struct in_range +{ + // assuming ascii part of UTF-8... + static_assert(Low <= Up, "lower bound should be less than upper bound."); + + static constexpr char upper = Up; + static constexpr char lower = Low; + + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + const auto first = loc.iter(); + + const char c = *(loc.iter()); + if(c < lower || upper < c) + { + return none(); + } + + loc.advance(); + return ok(region(loc, first, loc.iter())); + } +}; +template constexpr char in_range::upper; +template constexpr char in_range::lower; + +// keep iterator if `Combinator` matches. otherwise, increment `iter` by 1 char. +// for detecting invalid characters, like control sequences in toml string. +template +struct exclude +{ + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + auto first = loc.iter(); + + auto rslt = Combinator::invoke(loc); + if(rslt.is_ok()) + { + loc.reset(first); + return none(); + } + loc.reset(std::next(first)); // XXX maybe loc.advance() is okay but... + return ok(region(loc, first, loc.iter())); + } +}; + +// increment `iter`, if matches. otherwise, just return empty string. +template +struct maybe +{ + static result + invoke(location& loc) + { + const auto rslt = Combinator::invoke(loc); + if(rslt.is_ok()) + { + return rslt; + } + return ok(region(loc)); + } +}; + +template +struct sequence; + +template +struct sequence +{ + static result + invoke(location& loc) + { + const auto first = loc.iter(); + const auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + return sequence::invoke(loc, std::move(rslt.unwrap()), first); + } + + // called from the above function only, recursively. + template + static result + invoke(location& loc, region reg, Iterator first) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + reg += rslt.unwrap(); // concat regions + return sequence::invoke(loc, std::move(reg), first); + } +}; + +template +struct sequence +{ + // would be called from sequence::invoke only. + template + static result + invoke(location& loc, region reg, Iterator first) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + reg += rslt.unwrap(); // concat regions + return ok(reg); + } +}; + +template +struct either; + +template +struct either +{ + static result + invoke(location& loc) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_ok()) {return rslt;} + return either::invoke(loc); + } +}; +template +struct either +{ + static result + invoke(location& loc) + { + return Head::invoke(loc); + } +}; + +template +struct repeat; + +template struct exactly{}; +template struct at_least{}; +struct unlimited{}; + +template +struct repeat> +{ + static result + invoke(location& loc) + { + region retval(loc); + const auto first = loc.iter(); + for(std::size_t i=0; i +struct repeat> +{ + static result + invoke(location& loc) + { + region retval(loc); + + const auto first = loc.iter(); + for(std::size_t i=0; i +struct repeat +{ + static result + invoke(location& loc) + { + region retval(loc); + while(true) + { + auto rslt = T::invoke(loc); + if(rslt.is_err()) + { + return ok(std::move(retval)); + } + retval += rslt.unwrap(); + } + } +}; + +} // detail +} // toml +#endif// TOML11_COMBINATOR_HPP diff --git a/libraries/toml11/include/toml/comments.hpp b/libraries/toml11/include/toml/comments.hpp new file mode 100644 index 000000000..92fc8e138 --- /dev/null +++ b/libraries/toml11/include/toml/comments.hpp @@ -0,0 +1,466 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_COMMENTS_HPP +#define TOML11_COMMENTS_HPP +#include +#include +#include +#include +#include +#include +#include + +// This file provides mainly two classes, `preserve_comments` and `discard_comments`. +// Those two are a container that have the same interface as `std::vector` +// but bahaves in the opposite way. `preserve_comments` is just the same as +// `std::vector` and each `std::string` corresponds to a comment line. +// Conversely, `discard_comments` discards all the strings and ignores everything +// assigned in it. `discard_comments` is always empty and you will encounter an +// error whenever you access to the element. +namespace toml +{ +struct discard_comments; // forward decl + +// use it in the following way +// +// const toml::basic_value data = +// toml::parse("example.toml"); +// +// the interface is almost the same as std::vector. +struct preserve_comments +{ + // `container_type` is not provided in discard_comments. + // do not use this inner-type in a generic code. + using container_type = std::vector; + + using size_type = container_type::size_type; + using difference_type = container_type::difference_type; + using value_type = container_type::value_type; + using reference = container_type::reference; + using const_reference = container_type::const_reference; + using pointer = container_type::pointer; + using const_pointer = container_type::const_pointer; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using reverse_iterator = container_type::reverse_iterator; + using const_reverse_iterator = container_type::const_reverse_iterator; + + preserve_comments() = default; + ~preserve_comments() = default; + preserve_comments(preserve_comments const&) = default; + preserve_comments(preserve_comments &&) = default; + preserve_comments& operator=(preserve_comments const&) = default; + preserve_comments& operator=(preserve_comments &&) = default; + + explicit preserve_comments(const std::vector& c): comments(c){} + explicit preserve_comments(std::vector&& c) + : comments(std::move(c)) + {} + preserve_comments& operator=(const std::vector& c) + { + comments = c; + return *this; + } + preserve_comments& operator=(std::vector&& c) + { + comments = std::move(c); + return *this; + } + + explicit preserve_comments(const discard_comments&) {} + + explicit preserve_comments(size_type n): comments(n) {} + preserve_comments(size_type n, const std::string& x): comments(n, x) {} + preserve_comments(std::initializer_list x): comments(x) {} + template + preserve_comments(InputIterator first, InputIterator last) + : comments(first, last) + {} + + template + void assign(InputIterator first, InputIterator last) {comments.assign(first, last);} + void assign(std::initializer_list ini) {comments.assign(ini);} + void assign(size_type n, const std::string& val) {comments.assign(n, val);} + + // Related to the issue #97. + // + // It is known that `std::vector::insert` and `std::vector::erase` in + // the standard library implementation included in GCC 4.8.5 takes + // `std::vector::iterator` instead of `std::vector::const_iterator`. + // Because of the const-correctness, we cannot convert a `const_iterator` to + // an `iterator`. It causes compilation error in GCC 4.8.5. +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && !defined(__clang__) +# if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) <= 40805 +# define TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION +# endif +#endif + +#ifdef TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION + iterator insert(iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + void insert(iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + void insert(iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + void insert(iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(iterator pos) {return comments.erase(pos);} + iterator erase(iterator first, iterator last) + { + return comments.erase(first, last); + } +#else + iterator insert(const_iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(const_iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + iterator insert(const_iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + iterator insert(const_iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + iterator insert(const_iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(const_iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(const_iterator pos) {return comments.erase(pos);} + iterator erase(const_iterator first, const_iterator last) + { + return comments.erase(first, last); + } +#endif + + void swap(preserve_comments& other) {comments.swap(other.comments);} + + void push_back(const std::string& v) {comments.push_back(v);} + void push_back(std::string&& v) {comments.push_back(std::move(v));} + void pop_back() {comments.pop_back();} + + template + void emplace_back(Ts&& ... args) {comments.emplace_back(std::forward(args)...);} + + void clear() {comments.clear();} + + size_type size() const noexcept {return comments.size();} + size_type max_size() const noexcept {return comments.max_size();} + size_type capacity() const noexcept {return comments.capacity();} + bool empty() const noexcept {return comments.empty();} + + void reserve(size_type n) {comments.reserve(n);} + void resize(size_type n) {comments.resize(n);} + void resize(size_type n, const std::string& c) {comments.resize(n, c);} + void shrink_to_fit() {comments.shrink_to_fit();} + + reference operator[](const size_type n) noexcept {return comments[n];} + const_reference operator[](const size_type n) const noexcept {return comments[n];} + reference at(const size_type n) {return comments.at(n);} + const_reference at(const size_type n) const {return comments.at(n);} + reference front() noexcept {return comments.front();} + const_reference front() const noexcept {return comments.front();} + reference back() noexcept {return comments.back();} + const_reference back() const noexcept {return comments.back();} + + pointer data() noexcept {return comments.data();} + const_pointer data() const noexcept {return comments.data();} + + iterator begin() noexcept {return comments.begin();} + iterator end() noexcept {return comments.end();} + const_iterator begin() const noexcept {return comments.begin();} + const_iterator end() const noexcept {return comments.end();} + const_iterator cbegin() const noexcept {return comments.cbegin();} + const_iterator cend() const noexcept {return comments.cend();} + + reverse_iterator rbegin() noexcept {return comments.rbegin();} + reverse_iterator rend() noexcept {return comments.rend();} + const_reverse_iterator rbegin() const noexcept {return comments.rbegin();} + const_reverse_iterator rend() const noexcept {return comments.rend();} + const_reverse_iterator crbegin() const noexcept {return comments.crbegin();} + const_reverse_iterator crend() const noexcept {return comments.crend();} + + friend bool operator==(const preserve_comments&, const preserve_comments&); + friend bool operator!=(const preserve_comments&, const preserve_comments&); + friend bool operator< (const preserve_comments&, const preserve_comments&); + friend bool operator<=(const preserve_comments&, const preserve_comments&); + friend bool operator> (const preserve_comments&, const preserve_comments&); + friend bool operator>=(const preserve_comments&, const preserve_comments&); + + friend void swap(preserve_comments&, std::vector&); + friend void swap(std::vector&, preserve_comments&); + + private: + + container_type comments; +}; + +inline bool operator==(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments == rhs.comments;} +inline bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments != rhs.comments;} +inline bool operator< (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments < rhs.comments;} +inline bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments <= rhs.comments;} +inline bool operator> (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments > rhs.comments;} +inline bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments >= rhs.comments;} + +inline void swap(preserve_comments& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs); + return; +} +inline void swap(preserve_comments& lhs, std::vector& rhs) +{ + lhs.comments.swap(rhs); + return; +} +inline void swap(std::vector& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs.comments); + return; +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const preserve_comments& com) +{ + for(const auto& c : com) + { + os << '#' << c << '\n'; + } + return os; +} + +namespace detail +{ + +// To provide the same interface with `preserve_comments`, `discard_comments` +// should have an iterator. But it does not contain anything, so we need to +// add an iterator that points nothing. +// +// It always points null, so DO NOT unwrap this iterator. It always crashes +// your program. +template +struct empty_iterator +{ + using value_type = T; + using reference_type = typename std::conditional::type; + using pointer_type = typename std::conditional::type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + + empty_iterator() = default; + ~empty_iterator() = default; + empty_iterator(empty_iterator const&) = default; + empty_iterator(empty_iterator &&) = default; + empty_iterator& operator=(empty_iterator const&) = default; + empty_iterator& operator=(empty_iterator &&) = default; + + // DO NOT call these operators. + reference_type operator*() const noexcept {std::terminate();} + pointer_type operator->() const noexcept {return nullptr;} + reference_type operator[](difference_type) const noexcept {return this->operator*();} + + // These operators do nothing. + empty_iterator& operator++() noexcept {return *this;} + empty_iterator operator++(int) noexcept {return *this;} + empty_iterator& operator--() noexcept {return *this;} + empty_iterator operator--(int) noexcept {return *this;} + + empty_iterator& operator+=(difference_type) noexcept {return *this;} + empty_iterator& operator-=(difference_type) noexcept {return *this;} + + empty_iterator operator+(difference_type) const noexcept {return *this;} + empty_iterator operator-(difference_type) const noexcept {return *this;} +}; + +template +bool operator==(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator!=(const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator< (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator<=(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator> (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator>=(const empty_iterator&, const empty_iterator&) noexcept {return true;} + +template +typename empty_iterator::difference_type +operator-(const empty_iterator&, const empty_iterator&) noexcept {return 0;} + +template +empty_iterator +operator+(typename empty_iterator::difference_type, const empty_iterator& rhs) noexcept {return rhs;} +template +empty_iterator +operator+(const empty_iterator& lhs, typename empty_iterator::difference_type) noexcept {return lhs;} + +} // detail + +// The default comment type. It discards all the comments. It requires only one +// byte to contain, so the memory footprint is smaller than preserve_comments. +// +// It just ignores `push_back`, `insert`, `erase`, and any other modifications. +// IT always returns size() == 0, the iterator taken by `begin()` is always the +// same as that of `end()`, and accessing through `operator[]` or iterators +// always causes a segmentation fault. DO NOT access to the element of this. +// +// Why this is chose as the default type is because the last version (2.x.y) +// does not contain any comments in a value. To minimize the impact on the +// efficiency, this is choosed as a default. +// +// To reduce the memory footprint, later we can try empty base optimization (EBO). +struct discard_comments +{ + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using reference = std::string&; + using const_reference = std::string const&; + using pointer = std::string*; + using const_pointer = std::string const*; + using iterator = detail::empty_iterator; + using const_iterator = detail::empty_iterator; + using reverse_iterator = detail::empty_iterator; + using const_reverse_iterator = detail::empty_iterator; + + discard_comments() = default; + ~discard_comments() = default; + discard_comments(discard_comments const&) = default; + discard_comments(discard_comments &&) = default; + discard_comments& operator=(discard_comments const&) = default; + discard_comments& operator=(discard_comments &&) = default; + + explicit discard_comments(const std::vector&) noexcept {} + explicit discard_comments(std::vector&&) noexcept {} + discard_comments& operator=(const std::vector&) noexcept {return *this;} + discard_comments& operator=(std::vector&&) noexcept {return *this;} + + explicit discard_comments(const preserve_comments&) noexcept {} + + explicit discard_comments(size_type) noexcept {} + discard_comments(size_type, const std::string&) noexcept {} + discard_comments(std::initializer_list) noexcept {} + template + discard_comments(InputIterator, InputIterator) noexcept {} + + template + void assign(InputIterator, InputIterator) noexcept {} + void assign(std::initializer_list) noexcept {} + void assign(size_type, const std::string&) noexcept {} + + iterator insert(const_iterator, const std::string&) {return iterator{};} + iterator insert(const_iterator, std::string&&) {return iterator{};} + iterator insert(const_iterator, size_type, const std::string&) {return iterator{};} + template + iterator insert(const_iterator, InputIterator, InputIterator) {return iterator{};} + iterator insert(const_iterator, std::initializer_list) {return iterator{};} + + template + iterator emplace(const_iterator, Ts&& ...) {return iterator{};} + iterator erase(const_iterator) {return iterator{};} + iterator erase(const_iterator, const_iterator) {return iterator{};} + + void swap(discard_comments&) {return;} + + void push_back(const std::string&) {return;} + void push_back(std::string&& ) {return;} + void pop_back() {return;} + + template + void emplace_back(Ts&& ...) {return;} + + void clear() {return;} + + size_type size() const noexcept {return 0;} + size_type max_size() const noexcept {return 0;} + size_type capacity() const noexcept {return 0;} + bool empty() const noexcept {return true;} + + void reserve(size_type) {return;} + void resize(size_type) {return;} + void resize(size_type, const std::string&) {return;} + void shrink_to_fit() {return;} + + // DO NOT access to the element of this container. This container is always + // empty, so accessing through operator[], front/back, data causes address + // error. + + reference operator[](const size_type) noexcept {return *data();} + const_reference operator[](const size_type) const noexcept {return *data();} + reference at(const size_type) {throw std::out_of_range("toml::discard_comment is always empty.");} + const_reference at(const size_type) const {throw std::out_of_range("toml::discard_comment is always empty.");} + reference front() noexcept {return *data();} + const_reference front() const noexcept {return *data();} + reference back() noexcept {return *data();} + const_reference back() const noexcept {return *data();} + + pointer data() noexcept {return nullptr;} + const_pointer data() const noexcept {return nullptr;} + + iterator begin() noexcept {return iterator{};} + iterator end() noexcept {return iterator{};} + const_iterator begin() const noexcept {return const_iterator{};} + const_iterator end() const noexcept {return const_iterator{};} + const_iterator cbegin() const noexcept {return const_iterator{};} + const_iterator cend() const noexcept {return const_iterator{};} + + reverse_iterator rbegin() noexcept {return iterator{};} + reverse_iterator rend() noexcept {return iterator{};} + const_reverse_iterator rbegin() const noexcept {return const_iterator{};} + const_reverse_iterator rend() const noexcept {return const_iterator{};} + const_reverse_iterator crbegin() const noexcept {return const_iterator{};} + const_reverse_iterator crend() const noexcept {return const_iterator{};} +}; + +inline bool operator==(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator!=(const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator< (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator<=(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator> (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator>=(const discard_comments&, const discard_comments&) noexcept {return true;} + +inline void swap(const discard_comments&, const discard_comments&) noexcept {return;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const discard_comments&) +{ + return os; +} + +} // toml11 +#endif// TOML11_COMMENTS_HPP diff --git a/libraries/toml11/include/toml/datetime.hpp b/libraries/toml11/include/toml/datetime.hpp new file mode 100644 index 000000000..d8127c150 --- /dev/null +++ b/libraries/toml11/include/toml/datetime.hpp @@ -0,0 +1,631 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_DATETIME_HPP +#define TOML11_DATETIME_HPP +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace toml +{ + +// To avoid non-threadsafe std::localtime. In C11 (not C++11!), localtime_s is +// provided in the absolutely same purpose, but C++11 is actually not compatible +// with C11. We need to dispatch the function depending on the OS. +namespace detail +{ +// TODO: find more sophisticated way to handle this +#if (defined(_POSIX_C_SOURCE) && _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; + const auto result = ::localtime_r(src, &dst); + 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 defined(_MSC_VER) +inline std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_s(&dst, src); + if (result) { throw std::runtime_error("localtime_s failed."); } + return dst; +} +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 + +enum class month_t : std::uint8_t +{ + Jan = 0, + Feb = 1, + Mar = 2, + Apr = 3, + May = 4, + Jun = 5, + Jul = 6, + Aug = 7, + Sep = 8, + Oct = 9, + Nov = 10, + Dec = 11 +}; + +struct local_date +{ + std::int16_t year; // A.D. (like, 2018) + std::uint8_t month; // [0, 11] + std::uint8_t day; // [1, 31] + + local_date(int y, month_t m, int d) + : year (static_cast(y)), + month(static_cast(m)), + day (static_cast(d)) + {} + + explicit local_date(const std::tm& t) + : year (static_cast(t.tm_year + 1900)), + month(static_cast(t.tm_mon)), + day (static_cast(t.tm_mday)) + {} + + explicit local_date(const std::chrono::system_clock::time_point& tp) + { + const auto t = std::chrono::system_clock::to_time_t(tp); + const auto time = detail::localtime_s(&t); + *this = local_date(time); + } + + explicit local_date(const std::time_t t) + : local_date(std::chrono::system_clock::from_time_t(t)) + {} + + operator std::chrono::system_clock::time_point() const + { + // std::mktime returns date as local time zone. no conversion needed + std::tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = static_cast(this->day); + t.tm_mon = static_cast(this->month); + t.tm_year = static_cast(this->year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + return std::chrono::system_clock::from_time_t(std::mktime(&t)); + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + local_date() = default; + ~local_date() = default; + local_date(local_date const&) = default; + local_date(local_date&&) = default; + local_date& operator=(local_date const&) = default; + local_date& operator=(local_date&&) = default; +}; + +inline bool operator==(const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) == + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +inline bool operator!=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) < + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +inline bool operator<=(const local_date& lhs, const local_date& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_date& lhs, const local_date& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_date& date) +{ + os << std::setfill('0') << std::setw(4) << static_cast(date.year ) << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.month) + 1 << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.day ) ; + return os; +} + +struct local_time +{ + std::uint8_t hour; // [0, 23] + std::uint8_t minute; // [0, 59] + std::uint8_t second; // [0, 60] + std::uint16_t millisecond; // [0, 999] + std::uint16_t microsecond; // [0, 999] + std::uint16_t nanosecond; // [0, 999] + + local_time(int h, int m, int s, + int ms = 0, int us = 0, int ns = 0) + : hour (static_cast(h)), + minute(static_cast(m)), + second(static_cast(s)), + millisecond(static_cast(ms)), + microsecond(static_cast(us)), + nanosecond (static_cast(ns)) + {} + + explicit local_time(const std::tm& t) + : hour (static_cast(t.tm_hour)), + minute(static_cast(t.tm_min)), + second(static_cast(t.tm_sec)), + millisecond(0), microsecond(0), nanosecond(0) + {} + + template + explicit local_time(const std::chrono::duration& t) + { + const auto h = std::chrono::duration_cast(t); + this->hour = static_cast(h.count()); + const auto t2 = t - h; + const auto m = std::chrono::duration_cast(t2); + this->minute = static_cast(m.count()); + const auto t3 = t2 - m; + const auto s = std::chrono::duration_cast(t3); + this->second = static_cast(s.count()); + const auto t4 = t3 - s; + const auto ms = std::chrono::duration_cast(t4); + this->millisecond = static_cast(ms.count()); + const auto t5 = t4 - ms; + const auto us = std::chrono::duration_cast(t5); + this->microsecond = static_cast(us.count()); + const auto t6 = t5 - us; + const auto ns = std::chrono::duration_cast(t6); + this->nanosecond = static_cast(ns.count()); + } + + operator std::chrono::nanoseconds() const + { + return std::chrono::nanoseconds (this->nanosecond) + + std::chrono::microseconds(this->microsecond) + + std::chrono::milliseconds(this->millisecond) + + std::chrono::seconds(this->second) + + std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); + } + + local_time() = default; + ~local_time() = default; + local_time(local_time const&) = default; + local_time(local_time&&) = default; + local_time& operator=(local_time const&) = default; + local_time& operator=(local_time&&) = default; +}; + +inline bool operator==(const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) == + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +inline bool operator!=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) < + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +inline bool operator<=(const local_time& lhs, const local_time& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_time& lhs, const local_time& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_time& time) +{ + os << std::setfill('0') << std::setw(2) << static_cast(time.hour ) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.minute) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.second); + if(time.millisecond != 0 || time.microsecond != 0 || time.nanosecond != 0) + { + os << '.'; + os << std::setfill('0') << std::setw(3) << static_cast(time.millisecond); + if(time.microsecond != 0 || time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.microsecond); + if(time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.nanosecond); + } + } + } + return os; +} + +struct time_offset +{ + std::int8_t hour; // [-12, 12] + std::int8_t minute; // [-59, 59] + + time_offset(int h, int m) + : hour (static_cast(h)), + minute(static_cast(m)) + {} + + operator std::chrono::minutes() const + { + return std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); + } + + time_offset() = default; + ~time_offset() = default; + time_offset(time_offset const&) = default; + time_offset(time_offset&&) = default; + time_offset& operator=(time_offset const&) = default; + time_offset& operator=(time_offset&&) = default; +}; + +inline bool operator==(const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) == + std::make_tuple(rhs.hour, rhs.minute); +} +inline bool operator!=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) < + std::make_tuple(rhs.hour, rhs.minute); +} +inline bool operator<=(const time_offset& lhs, const time_offset& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const time_offset& offset) +{ + if(offset.hour == 0 && offset.minute == 0) + { + os << 'Z'; + return os; + } + int minute = static_cast(offset.hour) * 60 + offset.minute; + if(minute < 0){os << '-'; minute = std::abs(minute);} else {os << '+';} + os << std::setfill('0') << std::setw(2) << minute / 60 << ':'; + os << std::setfill('0') << std::setw(2) << minute % 60; + return os; +} + +struct local_datetime +{ + local_date date; + local_time time; + + local_datetime(local_date d, local_time t): date(d), time(t) {} + + explicit local_datetime(const std::tm& t): date(t), time(t){} + + explicit local_datetime(const std::chrono::system_clock::time_point& tp) + { + const auto t = std::chrono::system_clock::to_time_t(tp); + std::tm ltime = detail::localtime_s(&t); + + this->date = local_date(ltime); + this->time = local_time(ltime); + + // std::tm lacks subsecond information, so diff between tp and tm + // can be used to get millisecond & microsecond information. + const auto t_diff = tp - + std::chrono::system_clock::from_time_t(std::mktime(<ime)); + this->time.millisecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.microsecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.nanosecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + } + + explicit local_datetime(const std::time_t t) + : local_datetime(std::chrono::system_clock::from_time_t(t)) + {} + + operator std::chrono::system_clock::time_point() const + { + 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(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(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::from_time_t(std::mktime(&t)); + dt += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + return dt; + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + local_datetime() = default; + ~local_datetime() = default; + local_datetime(local_datetime const&) = default; + local_datetime(local_datetime&&) = default; + local_datetime& operator=(local_datetime const&) = default; + local_datetime& operator=(local_datetime&&) = default; +}; + +inline bool operator==(const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) == + std::make_tuple(rhs.date, rhs.time); +} +inline bool operator!=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) < + std::make_tuple(rhs.date, rhs.time); +} +inline bool operator<=(const local_datetime& lhs, const local_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_datetime& dt) +{ + os << dt.date << 'T' << dt.time; + return os; +} + +struct offset_datetime +{ + local_date date; + local_time time; + time_offset offset; + + offset_datetime(local_date d, local_time t, time_offset o) + : date(d), time(t), offset(o) + {} + offset_datetime(const local_datetime& dt, time_offset o) + : 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(nullptr)) + // use the current local timezone offset + {} + explicit offset_datetime(const std::chrono::system_clock::time_point& 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(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(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; + + // 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(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(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( + 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); + + // 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; + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + offset_datetime() = default; + ~offset_datetime() = default; + offset_datetime(offset_datetime const&) = default; + offset_datetime(offset_datetime&&) = default; + offset_datetime& operator=(offset_datetime const&) = default; + offset_datetime& operator=(offset_datetime&&) = default; + + private: + + static time_offset get_local_offset(const std::time_t* tp) + { + // get local timezone with the same date-time information as mktime + const auto t = detail::localtime_s(tp); + + std::array buf; + const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0 + if(result != 5) + { + throw std::runtime_error("toml::offset_datetime: cannot obtain " + "timezone information of current env"); + } + const int ofs = std::atoi(buf.data()); + const int ofs_h = ofs / 100; + const int ofs_m = ofs - (ofs_h * 100); + return time_offset(ofs_h, ofs_m); + } +}; + +inline bool operator==(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) == + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +inline bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) < + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +inline bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const offset_datetime& dt) +{ + os << dt.date << 'T' << dt.time << dt.offset; + return os; +} + +}//toml +#endif// TOML11_DATETIME diff --git a/libraries/toml11/include/toml/exception.hpp b/libraries/toml11/include/toml/exception.hpp new file mode 100644 index 000000000..c64651d0a --- /dev/null +++ b/libraries/toml11/include/toml/exception.hpp @@ -0,0 +1,65 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_EXCEPTION_HPP +#define TOML11_EXCEPTION_HPP +#include +#include + +#include "source_location.hpp" + +namespace toml +{ + +struct exception : public std::exception +{ + public: + explicit 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, 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();} + + protected: + std::string what_; +}; + +struct type_error : public toml::exception +{ + public: + 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();} + + protected: + std::string what_; +}; + +struct internal_error : public toml::exception +{ + public: + 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_; +}; + +} // toml +#endif // TOML_EXCEPTION diff --git a/libraries/toml11/include/toml/from.hpp b/libraries/toml11/include/toml/from.hpp new file mode 100644 index 000000000..8251973a3 --- /dev/null +++ b/libraries/toml11/include/toml/from.hpp @@ -0,0 +1,20 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_FROM_HPP +#define TOML11_FROM_HPP +#include "traits.hpp" + +namespace toml +{ + +template +struct from; +// { +// static T from_toml(const toml::value& v) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_FROM_HPP diff --git a/libraries/toml11/include/toml/get.hpp b/libraries/toml11/include/toml/get.hpp new file mode 100644 index 000000000..5095caf20 --- /dev/null +++ b/libraries/toml11/include/toml/get.hpp @@ -0,0 +1,1068 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_GET_HPP +#define TOML11_GET_HPP +#include + +#include "from.hpp" +#include "result.hpp" +#include "value.hpp" + +namespace toml +{ + +// ============================================================================ +// exact toml::* type + +template class M, template class V> +detail::enable_if_t>::value, T> & +get(basic_value& v) +{ + return v.template cast>::value>(); +} + +template class M, template class V> +detail::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + return v.template cast>::value>(); +} + +template class M, template class V> +detail::enable_if_t>::value, T> +get(basic_value&& v) +{ + return T(std::move(v).template cast>::value>()); +} + +// ============================================================================ +// T == toml::value; identity transformation. + +template class M, template class V> +inline detail::enable_if_t>::value, T>& +get(basic_value& v) +{ + return v; +} + +template class M, template class V> +inline detail::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + return v; +} + +template class M, template class V> +inline detail::enable_if_t>::value, T> +get(basic_value&& v) +{ + return basic_value(std::move(v)); +} + +// ============================================================================ +// T == toml::basic_value; basic_value -> basic_value + +template class M, template class V> +inline detail::enable_if_t, + detail::negation>> + >::value, T> +get(const basic_value& v) +{ + return T(v); +} + +// ============================================================================ +// integer convertible from toml::Integer + +template class M, template class V> +inline detail::enable_if_t, // T is integral + detail::negation>, // but not bool + detail::negation< // but not toml::integer + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_integer()); +} + +// ============================================================================ +// floating point convertible from toml::Float + +template class M, template class V> +inline detail::enable_if_t, // T is floating_point + detail::negation< // but not toml::floating + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_floating()); +} + +// ============================================================================ +// std::string; toml uses its own toml::string, but it should be convertible to +// std::string seamlessly + +template class M, template class V> +inline detail::enable_if_t::value, std::string>& +get(basic_value& v) +{ + return v.as_string().str; +} + +template class M, template class V> +inline detail::enable_if_t::value, std::string> const& +get(const basic_value& v) +{ + return v.as_string().str; +} + +template class M, template class V> +inline detail::enable_if_t::value, std::string> +get(basic_value&& v) +{ + return std::string(std::move(v.as_string().str)); +} + +// ============================================================================ +// std::string_view + +#if __cplusplus >= 201703L +template class M, template class V> +inline detail::enable_if_t::value, std::string_view> +get(const basic_value& v) +{ + return std::string_view(v.as_string().str); +} +#endif + +// ============================================================================ +// std::chrono::duration from toml::local_time. + +template class M, template class V> +inline detail::enable_if_t::value, T> +get(const basic_value& v) +{ + return std::chrono::duration_cast( + std::chrono::nanoseconds(v.as_local_time())); +} + +// ============================================================================ +// std::chrono::system_clock::time_point from toml::datetime variants + +template class M, template class V> +inline detail::enable_if_t< + std::is_same::value, T> +get(const basic_value& v) +{ + switch(v.type()) + { + case value_t::local_date: + { + return std::chrono::system_clock::time_point(v.as_local_date()); + } + case value_t::local_datetime: + { + return std::chrono::system_clock::time_point(v.as_local_datetime()); + } + case value_t::offset_datetime: + { + return std::chrono::system_clock::time_point(v.as_offset_datetime()); + } + default: + { + throw type_error(detail::format_underline("toml::value: " + "bad_cast to std::chrono::system_clock::time_point", { + {v.location(), concat_to_string("the actual type is ", v.type())} + }), v.location()); + } + } +} + +// ============================================================================ +// forward declaration to use this recursively. ignore this and go ahead. + +// array-like type with push_back(value) method +template class M, template class V> +detail::enable_if_t, // T is a container + detail::has_push_back_method, // T::push_back(value) works + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// array-like type without push_back(value) method +template class M, template class V> +detail::enable_if_t, // T is a container + detail::negation>, // w/o push_back(...) + detail::negation< // not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// std::pair +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value&); + +// std::tuple +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value&); + +// map-like classes +template class M, template class V> +detail::enable_if_t, // T is map + detail::negation< // but not toml::table + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// T.from_toml(v) +template class M, template class V> +detail::enable_if_t>>, + detail::has_from_toml_method, // but has from_toml(toml::value) + std::is_default_constructible // and default constructible + >::value, T> +get(const basic_value&); + +// toml::from::from_toml(v) +template class M, template class V, + std::size_t S = sizeof(::toml::from)> +T get(const basic_value&); + +// T(const toml::value&) and T is not toml::basic_value +template class M, template class V> +detail::enable_if_t>, + std::is_constructible&> + >::value, T> +get(const basic_value&); + +// ============================================================================ +// array-like types; most likely STL container, like std::vector, etc. + +template class M, template class V> +detail::enable_if_t, // T is a container + detail::has_push_back_method, // container.push_back(elem) works + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& ary = v.as_array(); + + T container; + try_reserve(container, ary.size()); + + for(const auto& elem : ary) + { + container.push_back(get(elem)); + } + return container; +} + +// ============================================================================ +// std::forward_list does not have push_back, insert, or emplace. +// It has insert_after, emplace_after, push_front. + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + T container; + for(const auto& elem : v.as_array()) + { + container.push_front(get(elem)); + } + container.reverse(); + return container; +} + +// ============================================================================ +// array-like types, without push_back(). most likely [std|boost]::array. + +template class M, template class V> +detail::enable_if_t, // T is a container + detail::negation>, // w/o push_back + detail::negation< // T is not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& ar = v.as_array(); + + T container; + if(ar.size() != container.size()) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified container size is ", container.size(), + " but there are ", ar.size(), " elements in toml array."), { + {v.location(), "here"} + })); + } + for(std::size_t i=0; i(ar[i]); + } + return container; +} + +// ============================================================================ +// std::pair. + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + using first_type = typename T::first_type; + using second_type = typename T::second_type; + + const auto& ar = v.as_array(); + if(ar.size() != 2) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified std::pair but there are ", ar.size(), + " elements in toml array."), {{v.location(), "here"}})); + } + return std::make_pair(::toml::get(ar.at(0)), + ::toml::get(ar.at(1))); +} + +// ============================================================================ +// std::tuple. + +namespace detail +{ +template +T get_tuple_impl(const Array& a, index_sequence) +{ + return std::make_tuple( + ::toml::get::type>(a.at(I))...); +} +} // detail + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + const auto& ar = v.as_array(); + if(ar.size() != std::tuple_size::value) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified std::tuple with ", + std::tuple_size::value, " elements, but there are ", ar.size(), + " elements in toml array."), {{v.location(), "here"}})); + } + return detail::get_tuple_impl(ar, + detail::make_index_sequence::value>{}); +} + +// ============================================================================ +// map-like types; most likely STL map, like std::map or std::unordered_map. + +template class M, template class V> +detail::enable_if_t, // T is map + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + static_assert(std::is_convertible::value, + "toml::get only supports map type of which key_type is " + "convertible from std::string."); + T map; + for(const auto& kv : v.as_table()) + { + map.emplace(key_type(kv.first), get(kv.second)); + } + return map; +} + +// ============================================================================ +// user-defined, but compatible types. + +template class M, template class V> +detail::enable_if_t>>, + detail::has_from_toml_method, // but has from_toml(toml::value) memfn + std::is_default_constructible // and default constructible + >::value, T> +get(const basic_value& v) +{ + T ud; + ud.from_toml(v); + return ud; +} +template class M, template class V, + std::size_t> +T get(const basic_value& v) +{ + return ::toml::from::from_toml(v); +} + +template class M, template class V> +detail::enable_if_t>, + std::is_constructible&> + >::value, T> +get(const basic_value& v) +{ + return T(v); +} + +// ============================================================================ +// find + +// ---------------------------------------------------------------------------- +// these overloads do not require to set T. and returns value itself. +template class M, template class V> +basic_value const& find(const basic_value& v, const key& ky) +{ + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return tab.at(ky); +} +template class M, template class V> +basic_value& find(basic_value& v, const key& ky) +{ + auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return tab.at(ky); +} +template class M, template class V> +basic_value find(basic_value&& v, const key& ky) +{ + typename basic_value::table_type tab = std::move(v).as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return basic_value(std::move(tab.at(ky))); +} + +// ---------------------------------------------------------------------------- +// find(value, idx) +template class M, template class V> +basic_value const& +find(const basic_value& v, const std::size_t idx) +{ + const auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ary.at(idx); +} +template class M, template class V> +basic_value& find(basic_value& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ary.at(idx); +} +template class M, template class V> +basic_value find(basic_value&& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return basic_value(std::move(ary.at(idx))); +} + +// ---------------------------------------------------------------------------- +// find(value, key); + +template class M, template class V> +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const key& ky) +{ + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(tab.at(ky)); +} + +template class M, template class V> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const key& ky) +{ + auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(tab.at(ky)); +} + +template class M, template class V> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const key& ky) +{ + typename basic_value::table_type tab = std::move(v).as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(std::move(tab.at(ky))); +} + +// ---------------------------------------------------------------------------- +// find(value, idx) +template class M, template class V> +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const std::size_t idx) +{ + const auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(ary.at(idx)); +} +template class M, template class V> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(ary.at(idx)); +} +template class M, template class V> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const std::size_t idx) +{ + typename basic_value::array_type ary = std::move(v).as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(std::move(ary.at(idx))); +} + +// -------------------------------------------------------------------------- +// toml::find(toml::value, toml::key, Ts&& ... keys) + +namespace detail +{ +// It suppresses warnings by -Wsign-conversion. Let's say we have the following +// code. +// ```cpp +// const auto x = toml::find(data, "array", 0); +// ``` +// Here, the type of literal number `0` is `int`. `int` is a signed integer. +// `toml::find` takes `std::size_t` as an index. So it causes implicit sign +// conversion and `-Wsign-conversion` warns about it. Using `0u` instead of `0` +// suppresses the warning, but it makes user code messy. +// To suppress this warning, we need to be aware of type conversion caused +// by `toml::find(v, key1, key2, ... keys)`. But the thing is that the types of +// keys can be any combination of {string-like, size_t-like}. Of course we can't +// write down all the combinations. Thus we need to use some function that +// recognize the type of argument and cast it into `std::string` or +// `std::size_t` depending on the context. +// `key_cast` does the job. It has 2 overloads. One is invoked when the +// argument type is an integer and cast the argument into `std::size_t`. The +// other is invoked when the argument type is not an integer, possibly one of +// std::string, const char[N] or const char*, and construct std::string from +// the argument. +// `toml::find(v, k1, k2, ... ks)` uses `key_cast` before passing `ks` to +// `toml::find(v, k)` to suppress -Wsign-conversion. + +template +enable_if_t>, + negation, bool>>>::value, std::size_t> +key_cast(T&& v) noexcept +{ + return std::size_t(v); +} +template +enable_if_t>, + negation, bool>>>>::value, std::string> +key_cast(T&& v) noexcept +{ + return std::string(std::forward(v)); +} +} // detail + +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +const basic_value& +find(const basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +basic_value& +find(basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +basic_value +find(basic_value&& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(std::move(v), std::forward(k1)), + detail::key_cast(k2), std::forward(keys)...); +} + +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&>())) +find(const basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(std::move(v), detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} + +// ============================================================================ +// get_or(value, fallback) + +template class M, template class V> +basic_value const& +get_or(const basic_value& v, const basic_value&) +{ + return v; +} +template class M, template class V> +basic_value& +get_or(basic_value& v, basic_value&) +{ + return v; +} +template class M, template class V> +basic_value +get_or(basic_value&& v, basic_value&&) +{ + return v; +} + +// ---------------------------------------------------------------------------- +// specialization for the exact toml types (return type becomes lvalue ref) + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T> const& +get_or(const basic_value& v, const T& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T>& +get_or(basic_value& v, T& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t, + basic_value>::value, detail::remove_cvref_t> +get_or(basic_value&& v, T&& opt) +{ + try + { + return get>(std::move(v)); + } + catch(...) + { + return detail::remove_cvref_t(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// specialization for std::string (return type becomes lvalue ref) + +template class M, template class V> +detail::enable_if_t, std::string>::value, + std::string> const& +get_or(const basic_value& v, const T& opt) +{ + try + { + return v.as_string().str; + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t::value, std::string>& +get_or(basic_value& v, T& opt) +{ + try + { + return v.as_string().str; + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t< + std::is_same, std::string>::value, std::string> +get_or(basic_value&& v, T&& opt) +{ + try + { + return std::move(v.as_string().str); + } + catch(...) + { + return std::string(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// specialization for string literal + +template class M, template class V> +detail::enable_if_t::type>::value, std::string> +get_or(const basic_value& v, T&& opt) +{ + try + { + return std::move(v.as_string().str); + } + catch(...) + { + return std::string(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// others (require type conversion and return type cannot be lvalue reference) + +template class M, template class V> +detail::enable_if_t, + basic_value>>, + detail::negation>>, + detail::negation::type>> + >::value, detail::remove_cvref_t> +get_or(const basic_value& v, T&& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return detail::remove_cvref_t(std::forward(opt)); + } +} + +// =========================================================================== +// find_or(value, key, fallback) + +template class M, template class V> +basic_value const& +find_or(const basic_value& v, const key& ky, + const basic_value& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return tab.at(ky); +} + +template class M, template class V> +basic_value& +find_or(basic_value& v, const toml::key& ky, basic_value& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return tab.at(ky); +} + +template class M, template class V> +basic_value +find_or(basic_value&& v, const toml::key& ky, basic_value&& opt) +{ + if(!v.is_table()) {return opt;} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return opt;} + return basic_value(std::move(tab.at(ky))); +} + +// --------------------------------------------------------------------------- +// exact types (return type can be a reference) +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T> const& +find_or(const basic_value& v, const key& ky, const T& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T>& +find_or(basic_value& v, const toml::key& ky, T& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, + detail::remove_cvref_t> +find_or(basic_value&& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(std::move(tab.at(ky)), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// std::string (return type can be a reference) + +template class M, template class V> +detail::enable_if_t::value, std::string> const& +find_or(const basic_value& v, const key& ky, const T& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} +template class M, template class V> +detail::enable_if_t::value, std::string>& +find_or(basic_value& v, const toml::key& ky, T& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} +template class M, template class V> +detail::enable_if_t::value, std::string> +find_or(basic_value&& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(std::move(tab.at(ky)), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// string literal (deduced as std::string) +template class M, template class V> +detail::enable_if_t< + detail::is_string_literal::type>::value, + std::string> +find_or(const basic_value& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::string(opt);} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return std::string(opt);} + return get_or(tab.at(ky), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// others (require type conversion and return type cannot be lvalue reference) +template class M, template class V> +detail::enable_if_t, basic_value>>, + // T is not std::string + detail::negation>>, + // T is not a string literal + detail::negation::type>> + >::value, detail::remove_cvref_t> +find_or(const basic_value& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(tab.at(ky), std::forward(opt)); +} + +// ============================================================================ +// expect + +template class M, template class V> +result expect(const basic_value& v) noexcept +{ + try + { + return ok(get(v)); + } + catch(const std::exception& e) + { + return err(e.what()); + } +} +template class M, template class V> +result +expect(const basic_value& v, const toml::key& k) noexcept +{ + try + { + return ok(find(v, k)); + } + catch(const std::exception& e) + { + return err(e.what()); + } +} + +} // toml +#endif// TOML11_GET diff --git a/libraries/toml11/include/toml/into.hpp b/libraries/toml11/include/toml/into.hpp new file mode 100644 index 000000000..17f22488e --- /dev/null +++ b/libraries/toml11/include/toml/into.hpp @@ -0,0 +1,20 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_INTO_HPP +#define TOML11_INTO_HPP +#include "traits.hpp" + +namespace toml +{ + +template +struct into; +// { +// static toml::value into_toml(const T& user_defined_type) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_INTO_HPP diff --git a/libraries/toml11/include/toml/lexer.hpp b/libraries/toml11/include/toml/lexer.hpp new file mode 100644 index 000000000..2eea996eb --- /dev/null +++ b/libraries/toml11/include/toml/lexer.hpp @@ -0,0 +1,270 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_LEXER_HPP +#define TOML11_LEXER_HPP +#include +#include +#include +#include + +#include "combinator.hpp" + +namespace toml +{ +namespace detail +{ + +// these scans contents from current location in a container of char +// and extract a region that matches their own pattern. +// to see the implementation of each component, see combinator.hpp. + +using lex_wschar = either, character<'\t'>>; +using lex_ws = repeat>; +using lex_newline = either, + sequence, character<'\n'>>>; +using lex_lower = in_range<'a', 'z'>; +using lex_upper = in_range<'A', 'Z'>; +using lex_alpha = either; +using lex_digit = in_range<'0', '9'>; +using lex_nonzero = in_range<'1', '9'>; +using lex_oct_dig = in_range<'0', '7'>; +using lex_bin_dig = in_range<'0', '1'>; +using lex_hex_dig = either, in_range<'a', 'f'>>; + +using lex_hex_prefix = sequence, character<'x'>>; +using lex_oct_prefix = sequence, character<'o'>>; +using lex_bin_prefix = sequence, character<'b'>>; +using lex_underscore = character<'_'>; +using lex_plus = character<'+'>; +using lex_minus = character<'-'>; +using lex_sign = either; + +// digit | nonzero 1*(digit | _ digit) +using lex_unsigned_dec_int = either>, at_least<1>>>, + lex_digit>; +// (+|-)? unsigned_dec_int +using lex_dec_int = sequence, lex_unsigned_dec_int>; + +// hex_prefix hex_dig *(hex_dig | _ hex_dig) +using lex_hex_int = sequence>, unlimited>>>; +// oct_prefix oct_dig *(oct_dig | _ oct_dig) +using lex_oct_int = sequence>, unlimited>>>; +// bin_prefix bin_dig *(bin_dig | _ bin_dig) +using lex_bin_int = sequence>, unlimited>>>; + +// (dec_int | hex_int | oct_int | bin_int) +using lex_integer = either; + +// =========================================================================== + +using lex_inf = sequence, character<'n'>, character<'f'>>; +using lex_nan = sequence, character<'a'>, character<'n'>>; +using lex_special_float = sequence, either>; + +using lex_zero_prefixable_int = sequence>, unlimited>>; + +using lex_fractional_part = sequence, lex_zero_prefixable_int>; + +using lex_exponent_part = sequence, character<'E'>>, + maybe, lex_zero_prefixable_int>; + +using lex_float = either>>>>; + +// =========================================================================== + +using lex_true = sequence, character<'r'>, + character<'u'>, character<'e'>>; +using lex_false = sequence, character<'a'>, character<'l'>, + character<'s'>, character<'e'>>; +using lex_boolean = either; + +// =========================================================================== + +using lex_date_fullyear = repeat>; +using lex_date_month = repeat>; +using lex_date_mday = repeat>; +using lex_time_delim = either, character<'t'>, character<' '>>; +using lex_time_hour = repeat>; +using lex_time_minute = repeat>; +using lex_time_second = repeat>; +using lex_time_secfrac = sequence, + repeat>>; + +using lex_time_numoffset = sequence, character<'-'>>, + sequence, + lex_time_minute>>; +using lex_time_offset = either, character<'z'>, + lex_time_numoffset>; + +using lex_partial_time = sequence, + lex_time_minute, character<':'>, + lex_time_second, maybe>; +using lex_full_date = sequence, + lex_date_month, character<'-'>, + lex_date_mday>; +using lex_full_time = sequence; + +using lex_offset_date_time = sequence; +using lex_local_date_time = sequence; +using lex_local_date = lex_full_date; +using lex_local_time = lex_partial_time; + +// =========================================================================== + +using lex_quotation_mark = character<'"'>; +using lex_basic_unescaped = exclude, // 0x09 (tab) + in_range<0x0a, 0x1F>, // is allowed + character<0x22>, character<0x5C>, + character<0x7F>>>; + +using lex_escape = character<'\\'>; +using lex_escape_unicode_short = sequence, + repeat>>; +using lex_escape_unicode_long = sequence, + repeat>>; +using lex_escape_seq_char = either, character<'\\'>, + character<'b'>, character<'f'>, + character<'n'>, character<'r'>, + character<'t'>, + lex_escape_unicode_short, + lex_escape_unicode_long + >; +using lex_escaped = sequence; +using lex_basic_char = either; +using lex_basic_string = sequence, + lex_quotation_mark>; + +// After toml post-v0.5.0, it is explicitly clarified how quotes in ml-strings +// are allowed to be used. +// After this, the following strings are *explicitly* allowed. +// - One or two `"`s in a multi-line basic string is allowed wherever it is. +// - Three consecutive `"`s in a multi-line basic string is considered as a delimiter. +// - One or two `"`s can appear just before or after the delimiter. +// ```toml +// str4 = """Here are two quotation marks: "". Simple enough.""" +// str5 = """Here are three quotation marks: ""\".""" +// str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" +// str7 = """"This," she said, "is just a pointless statement."""" +// ``` +// In the current implementation (v3.3.0), it is difficult to parse `str7` in +// the above example. It is difficult to recognize `"` at the end of string body +// collectly. It will be misunderstood as a `"""` delimiter and an additional, +// invalid `"`. Like this: +// ```console +// what(): [error] toml::parse_table: invalid line format +// --> hoge.toml +// | +// 13 | str7 = """"This," she said, "is just a pointless statement."""" +// | ^- expected newline, but got '"'. +// ``` +// As a quick workaround for this problem, `lex_ml_basic_string_delim` was +// splitted into two, `lex_ml_basic_string_open` and `lex_ml_basic_string_close`. +// `lex_ml_basic_string_open` allows only `"""`. `_close` allows 3-5 `"`s. +// In parse_ml_basic_string() function, the trailing `"`s will be attached to +// the string body. +// +using lex_ml_basic_string_delim = repeat>; +using lex_ml_basic_string_open = lex_ml_basic_string_delim; +using lex_ml_basic_string_close = sequence< + repeat>, + maybe, maybe + >; + +using lex_ml_basic_unescaped = exclude, // 0x09 + in_range<0x0a, 0x1F>, // is tab + character<0x5C>, // backslash + character<0x7F>, // DEL + lex_ml_basic_string_delim>>; + +using lex_ml_basic_escaped_newline = sequence< + lex_escape, maybe, lex_newline, + repeat, unlimited>>; + +using lex_ml_basic_char = either; +using lex_ml_basic_body = repeat, + unlimited>; +using lex_ml_basic_string = sequence; + +using lex_literal_char = exclude, + in_range<0x10, 0x19>, character<0x27>>>; +using lex_apostrophe = character<'\''>; +using lex_literal_string = sequence, + lex_apostrophe>; + +// the same reason as above. +using lex_ml_literal_string_delim = repeat>; +using lex_ml_literal_string_open = lex_ml_literal_string_delim; +using lex_ml_literal_string_close = sequence< + repeat>, + maybe, maybe + >; + +using lex_ml_literal_char = exclude, + in_range<0x10, 0x1F>, + character<0x7F>, + lex_ml_literal_string_delim>>; +using lex_ml_literal_body = repeat, + unlimited>; +using lex_ml_literal_string = sequence; + +using lex_string = either; + +// =========================================================================== + +using lex_comment_start_symbol = character<'#'>; +using lex_non_eol = either, exclude>>; +using lex_comment = sequence>; + +using lex_dot_sep = sequence, character<'.'>, maybe>; + +using lex_unquoted_key = repeat, character<'_'>>, + at_least<1>>; +using lex_quoted_key = either; +using lex_simple_key = either; +using lex_dotted_key = sequence, + at_least<1> + > + >; +using lex_key = either; + +using lex_keyval_sep = sequence, + character<'='>, + maybe>; + +using lex_std_table_open = character<'['>; +using lex_std_table_close = character<']'>; +using lex_std_table = sequence, + lex_key, + maybe, + lex_std_table_close>; + +using lex_array_table_open = sequence; +using lex_array_table_close = sequence; +using lex_array_table = sequence, + lex_key, + maybe, + lex_array_table_close>; + +} // detail +} // toml +#endif // TOML_LEXER_HPP diff --git a/libraries/toml11/include/toml/literal.hpp b/libraries/toml11/include/toml/literal.hpp new file mode 100644 index 000000000..0824f8385 --- /dev/null +++ b/libraries/toml11/include/toml/literal.hpp @@ -0,0 +1,112 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_LITERAL_HPP +#define TOML11_LITERAL_HPP +#include "parser.hpp" + +namespace toml +{ +inline namespace literals +{ +inline namespace toml_literals +{ + +// implementation +inline ::toml::basic_value<::toml::discard_comments, std::unordered_map, std::vector> +literal_internal_impl(::toml::detail::location loc) +{ + using value_type = ::toml::basic_value< + ::toml::discard_comments, std::unordered_map, std::vector>; + // if there are some comments or empty lines, skip them. + using skip_line = ::toml::detail::repeat, + ::toml::detail::maybe<::toml::detail::lex_comment>, + ::toml::detail::lex_newline + >, ::toml::detail::at_least<1>>; + skip_line::invoke(loc); + + // if there are some whitespaces before a value, skip them. + using skip_ws = ::toml::detail::repeat< + ::toml::detail::lex_ws, ::toml::detail::at_least<1>>; + skip_ws::invoke(loc); + + // to distinguish arrays and tables, first check it is a table or not. + // + // "[1,2,3]"_toml; // this is an array + // "[table]"_toml; // a table that has an empty table named "table" inside. + // "[[1,2,3]]"_toml; // this is an array of arrays + // "[[table]]"_toml; // this is a table that has an array of tables inside. + // + // "[[1]]"_toml; // this can be both... (currently it becomes a table) + // "1 = [{}]"_toml; // this is a table that has an array of table named 1. + // "[[1,]]"_toml; // this is an array of arrays. + // "[[1],]"_toml; // this also. + + const auto the_front = loc.iter(); + + const bool is_table_key = ::toml::detail::lex_std_table::invoke(loc); + loc.reset(the_front); + + const bool is_aots_key = ::toml::detail::lex_array_table::invoke(loc); + loc.reset(the_front); + + // If it is neither a table-key or a array-of-table-key, it may be a value. + if(!is_table_key && !is_aots_key) + { + if(auto data = ::toml::detail::parse_value(loc)) + { + return data.unwrap(); + } + } + + // Note that still it can be a table, because the literal might be something + // like the following. + // ```cpp + // R"( // c++11 raw string literals + // key = "value" + // int = 42 + // )"_toml; + // ``` + // It is a valid toml file. + // It should be parsed as if we parse a file with this content. + + if(auto data = ::toml::detail::parse_toml_file(loc)) + { + return data.unwrap(); + } + else // none of them. + { + throw ::toml::syntax_error(data.unwrap_err(), source_location(loc)); + } + +} + +inline ::toml::basic_value<::toml::discard_comments, std::unordered_map, std::vector> +operator"" _toml(const char* str, std::size_t len) +{ + ::toml::detail::location loc( + std::string("TOML literal encoded in a C++ code"), + std::vector(str, str + len)); + return literal_internal_impl(std::move(loc)); +} + +// value of __cplusplus in C++2a/20 mode is not fixed yet along compilers. +// So here we use the feature test macro for `char8_t` itself. +#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L +// value of u8"" literal has been changed from char to char8_t and char8_t is +// NOT compatible to char +inline ::toml::basic_value<::toml::discard_comments, std::unordered_map, std::vector> +operator"" _toml(const char8_t* str, std::size_t len) +{ + ::toml::detail::location loc( + std::string("TOML literal encoded in a C++ code"), + std::vector(reinterpret_cast(str), + reinterpret_cast(str) + len)); + return literal_internal_impl(std::move(loc)); +} +#endif + +} // toml_literals +} // literals +} // toml +#endif//TOML11_LITERAL_HPP diff --git a/libraries/toml11/include/toml/macros.hpp b/libraries/toml11/include/toml/macros.hpp new file mode 100644 index 000000000..e8f91aecd --- /dev/null +++ b/libraries/toml11/include/toml/macros.hpp @@ -0,0 +1,121 @@ +#ifndef TOML11_MACROS_HPP +#define TOML11_MACROS_HPP + +#define TOML11_STRINGIZE_AUX(x) #x +#define TOML11_STRINGIZE(x) TOML11_STRINGIZE_AUX(x) + +#define TOML11_CONCATENATE_AUX(x, y) x##y +#define TOML11_CONCATENATE(x, y) TOML11_CONCATENATE_AUX(x, y) + +// ============================================================================ +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +#ifndef TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +// ---------------------------------------------------------------------------- +// TOML11_ARGS_SIZE + +#define TOML11_INDEX_RSEQ() \ + 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \ + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#define TOML11_ARGS_SIZE_IMPL(\ + ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9, ARG10, \ + ARG11, ARG12, ARG13, ARG14, ARG15, ARG16, ARG17, ARG18, ARG19, ARG20, \ + ARG21, ARG22, ARG23, ARG24, ARG25, ARG26, ARG27, ARG28, ARG29, ARG30, \ + ARG31, ARG32, N, ...) N +#define TOML11_ARGS_SIZE_AUX(...) TOML11_ARGS_SIZE_IMPL(__VA_ARGS__) +#define TOML11_ARGS_SIZE(...) TOML11_ARGS_SIZE_AUX(__VA_ARGS__, TOML11_INDEX_RSEQ()) + +// ---------------------------------------------------------------------------- +// TOML11_FOR_EACH_VA_ARGS + +#define TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, ARG1 ) FUNCTOR(ARG1) +#define TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_32(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, __VA_ARGS__) + +#define TOML11_FOR_EACH_VA_ARGS(FUNCTOR, ...)\ + TOML11_CONCATENATE(TOML11_FOR_EACH_VA_ARGS_AUX_, TOML11_ARGS_SIZE(__VA_ARGS__))(FUNCTOR, __VA_ARGS__) + +// ---------------------------------------------------------------------------- +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +// use it in the following way. +// ```cpp +// namespace foo +// { +// struct Foo +// { +// std::string s; +// double d; +// int i; +// }; +// } // foo +// +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i) +// ``` +// And then you can use `toml::find(file, "foo");` +// +#define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME)\ + obj.VAR_NAME = toml::find(v, TOML11_STRINGIZE(VAR_NAME)); + +#define TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE(VAR_NAME)\ + v[TOML11_STRINGIZE(VAR_NAME)] = obj.VAR_NAME; + +#define TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(NAME, ...)\ + namespace toml { \ + template<> \ + struct from \ + { \ + template class T, \ + template class A> \ + static NAME from_toml(const basic_value& v) \ + { \ + NAME obj; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, __VA_ARGS__) \ + return obj; \ + } \ + }; \ + template<> \ + struct into \ + { \ + static value into_toml(const NAME& obj) \ + { \ + ::toml::value v = ::toml::table{}; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE, __VA_ARGS__) \ + return v; \ + } \ + }; \ + } /* toml */ + +#endif// TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +#endif// TOML11_MACROS_HPP diff --git a/libraries/toml11/include/toml/parser.hpp b/libraries/toml11/include/toml/parser.hpp new file mode 100644 index 000000000..c3df644e1 --- /dev/null +++ b/libraries/toml11/include/toml/parser.hpp @@ -0,0 +1,2177 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_PARSER_HPP +#define TOML11_PARSER_HPP +#include +#include +#include + +#include "combinator.hpp" +#include "lexer.hpp" +#include "region.hpp" +#include "result.hpp" +#include "types.hpp" +#include "value.hpp" + +#ifndef TOML11_DISABLE_STD_FILESYSTEM +#ifdef __cpp_lib_filesystem +#if __has_include() +#define TOML11_HAS_STD_FILESYSTEM +#include +#endif // has_include() +#endif // __cpp_lib_filesystem +#endif // TOML11_DISABLE_STD_FILESYSTEM + +namespace toml +{ +namespace detail +{ + +inline result, std::string> +parse_boolean(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_boolean::invoke(loc)) + { + const auto reg = token.unwrap(); + if (reg.str() == "true") {return ok(std::make_pair(true, reg));} + else if(reg.str() == "false") {return ok(std::make_pair(false, reg));} + else // internal error. + { + throw internal_error(format_underline( + "toml::parse_boolean: internal error", + {{source_location(reg), "invalid token"}}), + source_location(reg)); + } + } + loc.reset(first); //rollback + return err(format_underline("toml::parse_boolean: ", + {{source_location(loc), "the next token is not a boolean"}})); +} + +inline result, std::string> +parse_binary_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_bin_int::invoke(loc)) + { + auto str = token.unwrap().str(); + assert(str.size() > 2); // minimum -> 0b1 + integer retval(0), base(1); + for(auto i(str.rbegin()), e(str.rend() - 2); i!=e; ++i) + { + if (*i == '1'){retval += base; base *= 2;} + else if(*i == '0'){base *= 2;} + else if(*i == '_'){/* do nothing. */} + else // internal error. + { + throw internal_error(format_underline( + "toml::parse_integer: internal error", + {{source_location(token.unwrap()), "invalid token"}}), + source_location(loc)); + } + } + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_binary_integer:", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_octal_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_oct_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + str.erase(str.begin()); str.erase(str.begin()); // remove `0o` prefix + + std::istringstream iss(str); + integer retval(0); + iss >> std::oct >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_octal_integer:", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_hexadecimal_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_hex_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + str.erase(str.begin()); str.erase(str.begin()); // remove `0x` prefix + + std::istringstream iss(str); + integer retval(0); + iss >> std::hex >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_hexadecimal_integer", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_integer(location& loc) +{ + const auto first = loc.iter(); + if(first != loc.end() && *first == '0') + { + const auto second = std::next(first); + if(second == loc.end()) // the token is just zero. + { + loc.advance(); + return ok(std::make_pair(0, region(loc, first, second))); + } + + if(*second == 'b') {return parse_binary_integer (loc);} // 0b1100 + if(*second == 'o') {return parse_octal_integer (loc);} // 0o775 + if(*second == 'x') {return parse_hexadecimal_integer(loc);} // 0xC0FFEE + + if(std::isdigit(*second)) + { + return err(format_underline("toml::parse_integer: " + "leading zero in an Integer is not allowed.", + {{source_location(loc), "leading zero"}})); + } + else if(std::isalpha(*second)) + { + return err(format_underline("toml::parse_integer: " + "unknown integer prefix appeared.", + {{source_location(loc), "none of 0x, 0o, 0b"}})); + } + } + + if(const auto token = lex_dec_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + std::istringstream iss(str); + integer retval(0); + iss >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_integer: ", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_floating(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_float::invoke(loc)) + { + auto str = token.unwrap().str(); + if(str == "inf" || str == "+inf") + { + if(std::numeric_limits::has_infinity) + { + return ok(std::make_pair( + std::numeric_limits::infinity(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "-inf") + { + if(std::numeric_limits::has_infinity) + { + return ok(std::make_pair( + -std::numeric_limits::infinity(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "nan" || str == "+nan") + { + if(std::numeric_limits::has_quiet_NaN) + { + return ok(std::make_pair( + std::numeric_limits::quiet_NaN(), token.unwrap())); + } + else if(std::numeric_limits::has_signaling_NaN) + { + return ok(std::make_pair( + std::numeric_limits::signaling_NaN(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "-nan") + { + if(std::numeric_limits::has_quiet_NaN) + { + return ok(std::make_pair( + -std::numeric_limits::quiet_NaN(), token.unwrap())); + } + else if(std::numeric_limits::has_signaling_NaN) + { + return ok(std::make_pair( + -std::numeric_limits::signaling_NaN(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + std::istringstream iss(str); + floating v(0.0); + iss >> v; + return ok(std::make_pair(v, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_floating: ", + {{source_location(loc), "the next token is not a float"}})); +} + +inline std::string read_utf8_codepoint(const region& reg, const location& loc) +{ + const auto str = reg.str().substr(1); + std::uint_least32_t codepoint; + std::istringstream iss(str); + iss >> std::hex >> codepoint; + + const auto to_char = [](const std::uint_least32_t i) noexcept -> char { + const auto uc = static_cast(i); + return *reinterpret_cast(std::addressof(uc)); + }; + + std::string character; + if(codepoint < 0x80) // U+0000 ... U+0079 ; just an ASCII. + { + character += static_cast(codepoint); + } + else if(codepoint < 0x800) //U+0080 ... U+07FF + { + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + character += to_char(0xC0| codepoint >> 6); + character += to_char(0x80|(codepoint & 0x3F)); + } + else if(codepoint < 0x10000) // U+0800...U+FFFF + { + if(0xD800 <= codepoint && codepoint <= 0xDFFF) + { + throw syntax_error(format_underline( + "toml::read_utf8_codepoint: codepoints in the range " + "[0xD800, 0xDFFF] are not valid UTF-8.", {{ + source_location(loc), "not a valid UTF-8 codepoint" + }}), source_location(loc)); + } + assert(codepoint < 0xD800 || 0xDFFF < codepoint); + // 1110yyyy 10yxxxxx 10xxxxxx + character += to_char(0xE0| codepoint >> 12); + character += to_char(0x80|(codepoint >> 6 & 0x3F)); + character += to_char(0x80|(codepoint & 0x3F)); + } + else if(codepoint < 0x110000) // U+010000 ... U+10FFFF + { + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + character += to_char(0xF0| codepoint >> 18); + character += to_char(0x80|(codepoint >> 12 & 0x3F)); + character += to_char(0x80|(codepoint >> 6 & 0x3F)); + character += to_char(0x80|(codepoint & 0x3F)); + } + else // out of UTF-8 region + { + throw syntax_error(format_underline("toml::read_utf8_codepoint:" + " input codepoint is too large.", + {{source_location(loc), "should be in [0x00..0x10FFFF]"}}), + source_location(loc)); + } + return character; +} + +inline result parse_escape_sequence(location& loc) +{ + const auto first = loc.iter(); + if(first == loc.end() || *first != '\\') + { + return err(format_underline("toml::parse_escape_sequence: ", {{ + source_location(loc), "the next token is not a backslash \"\\\""}})); + } + loc.advance(); + switch(*loc.iter()) + { + case '\\':{loc.advance(); return ok(std::string("\\"));} + case '"' :{loc.advance(); return ok(std::string("\""));} + case 'b' :{loc.advance(); return ok(std::string("\b"));} + case 't' :{loc.advance(); return ok(std::string("\t"));} + case 'n' :{loc.advance(); return ok(std::string("\n"));} + case 'f' :{loc.advance(); return ok(std::string("\f"));} + case 'r' :{loc.advance(); return ok(std::string("\r"));} + case 'u' : + { + if(const auto token = lex_escape_unicode_short::invoke(loc)) + { + return ok(read_utf8_codepoint(token.unwrap(), loc)); + } + else + { + return err(format_underline("parse_escape_sequence: " + "invalid token found in UTF-8 codepoint uXXXX.", + {{source_location(loc), "here"}})); + } + } + case 'U': + { + if(const auto token = lex_escape_unicode_long::invoke(loc)) + { + return ok(read_utf8_codepoint(token.unwrap(), loc)); + } + else + { + return err(format_underline("parse_escape_sequence: " + "invalid token found in UTF-8 codepoint Uxxxxxxxx", + {{source_location(loc), "here"}})); + } + } + } + + const auto msg = format_underline("parse_escape_sequence: " + "unknown escape sequence appeared.", {{source_location(loc), + "escape sequence is one of \\, \", b, t, n, f, r, uxxxx, Uxxxxxxxx"}}, + /* Hints = */{"if you want to write backslash as just one backslash, " + "use literal string like: regex = '<\\i\\c*\\s*>'"}); + loc.reset(first); + return err(msg); +} + +inline result, std::string> +parse_ml_basic_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_ml_basic_string::invoke(loc)) + { + auto inner_loc = loc; + inner_loc.reset(first); + + std::string retval; + retval.reserve(token.unwrap().size()); + + auto delim = lex_ml_basic_string_open::invoke(inner_loc); + if(!delim) + { + throw internal_error(format_underline( + "parse_ml_basic_string: invalid token", + {{source_location(inner_loc), "should be \"\"\""}}), + source_location(inner_loc)); + } + // immediate newline is ignored (if exists) + /* discard return value */ lex_newline::invoke(inner_loc); + + delim = none(); + while(!delim) + { + using lex_unescaped_seq = repeat< + either, unlimited>; + if(auto unescaped = lex_unescaped_seq::invoke(inner_loc)) + { + retval += unescaped.unwrap().str(); + } + if(auto escaped = parse_escape_sequence(inner_loc)) + { + retval += escaped.unwrap(); + } + if(auto esc_nl = lex_ml_basic_escaped_newline::invoke(inner_loc)) + { + // ignore newline after escape until next non-ws char + } + if(inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "parse_ml_basic_string: unexpected end of region", + {{source_location(inner_loc), "not sufficient token"}}), + source_location(inner_loc)); + } + delim = lex_ml_basic_string_close::invoke(inner_loc); + } + // `lex_ml_basic_string_close` allows 3 to 5 `"`s to allow 1 or 2 `"`s + // at just before the delimiter. Here, we need to attach `"`s at the + // end of the string body, if it exists. + // For detail, see the definition of `lex_ml_basic_string_close`. + assert(std::all_of(delim.unwrap().first(), delim.unwrap().last(), + [](const char c) noexcept {return c == '\"';})); + switch(delim.unwrap().size()) + { + case 3: {break;} + case 4: {retval += "\""; break;} + case 5: {retval += "\"\""; break;} + default: + { + throw internal_error(format_underline( + "parse_ml_basic_string: closing delimiter has invalid length", + {{source_location(inner_loc), "end of this"}}), + source_location(inner_loc)); + } + } + return ok(std::make_pair(toml::string(retval), token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_ml_basic_string: " + "the next token is not a valid multiline string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_basic_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_basic_string::invoke(loc)) + { + auto inner_loc = loc; + inner_loc.reset(first); + + auto quot = lex_quotation_mark::invoke(inner_loc); + if(!quot) + { + throw internal_error(format_underline("parse_basic_string: " + "invalid token", {{source_location(inner_loc), "should be \""}}), + source_location(inner_loc)); + } + + std::string retval; + retval.reserve(token.unwrap().size()); + + quot = none(); + while(!quot) + { + using lex_unescaped_seq = repeat; + if(auto unescaped = lex_unescaped_seq::invoke(inner_loc)) + { + retval += unescaped.unwrap().str(); + } + if(auto escaped = parse_escape_sequence(inner_loc)) + { + retval += escaped.unwrap(); + } + if(inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "parse_basic_string: unexpected end of region", + {{source_location(inner_loc), "not sufficient token"}}), + source_location(inner_loc)); + } + quot = lex_quotation_mark::invoke(inner_loc); + } + return ok(std::make_pair(toml::string(retval), token.unwrap())); + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_basic_string: " + "the next token is not a valid string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_ml_literal_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_ml_literal_string::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_ml_literal_string_open::invoke(inner_loc); + if(!open) + { + throw internal_error(format_underline( + "parse_ml_literal_string: invalid token", + {{source_location(inner_loc), "should be '''"}}), + source_location(inner_loc)); + } + // immediate newline is ignored (if exists) + /* discard return value */ lex_newline::invoke(inner_loc); + + const auto body = lex_ml_literal_body::invoke(inner_loc); + + const auto close = lex_ml_literal_string_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "parse_ml_literal_string: invalid token", + {{source_location(inner_loc), "should be '''"}}), + source_location(inner_loc)); + } + // `lex_ml_literal_string_close` allows 3 to 5 `'`s to allow 1 or 2 `'`s + // at just before the delimiter. Here, we need to attach `'`s at the + // end of the string body, if it exists. + // For detail, see the definition of `lex_ml_basic_string_close`. + + std::string retval = body.unwrap().str(); + assert(std::all_of(close.unwrap().first(), close.unwrap().last(), + [](const char c) noexcept {return c == '\'';})); + switch(close.unwrap().size()) + { + case 3: {break;} + case 4: {retval += "'"; break;} + case 5: {retval += "''"; break;} + default: + { + throw internal_error(format_underline( + "parse_ml_literal_string: closing delimiter has invalid length", + {{source_location(inner_loc), "end of this"}}), + source_location(inner_loc)); + } + } + return ok(std::make_pair(toml::string(retval, toml::string_t::literal), + token.unwrap())); + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_ml_literal_string: " + "the next token is not a valid multiline literal string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_literal_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_literal_string::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_apostrophe::invoke(inner_loc); + if(!open) + { + throw internal_error(format_underline( + "parse_literal_string: invalid token", + {{source_location(inner_loc), "should be '"}}), + source_location(inner_loc)); + } + + const auto body = repeat::invoke(inner_loc); + + const auto close = lex_apostrophe::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "parse_literal_string: invalid token", + {{source_location(inner_loc), "should be '"}}), + source_location(inner_loc)); + } + return ok(std::make_pair( + toml::string(body.unwrap().str(), toml::string_t::literal), + token.unwrap())); + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_literal_string: " + "the next token is not a valid literal string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_string(location& loc) +{ + if(loc.iter() != loc.end() && *(loc.iter()) == '"') + { + if(loc.iter() + 1 != loc.end() && *(loc.iter() + 1) == '"' && + loc.iter() + 2 != loc.end() && *(loc.iter() + 2) == '"') + { + return parse_ml_basic_string(loc); + } + else + { + return parse_basic_string(loc); + } + } + else if(loc.iter() != loc.end() && *(loc.iter()) == '\'') + { + if(loc.iter() + 1 != loc.end() && *(loc.iter() + 1) == '\'' && + loc.iter() + 2 != loc.end() && *(loc.iter() + 2) == '\'') + { + return parse_ml_literal_string(loc); + } + else + { + return parse_literal_string(loc); + } + } + return err(format_underline("toml::parse_string: ", + {{source_location(loc), "the next token is not a string"}})); +} + +inline result, std::string> +parse_local_date(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_date::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto y = lex_date_fullyear::invoke(inner_loc); + if(!y || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != '-') + { + throw internal_error(format_underline( + "toml::parse_inner_local_date: invalid year format", + {{source_location(inner_loc), "should be `-`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto m = lex_date_month::invoke(inner_loc); + if(!m || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != '-') + { + throw internal_error(format_underline( + "toml::parse_local_date: invalid month format", + {{source_location(inner_loc), "should be `-`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto d = lex_date_mday::invoke(inner_loc); + if(!d) + { + throw internal_error(format_underline( + "toml::parse_local_date: invalid day format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + return ok(std::make_pair(local_date( + static_cast(from_string(y.unwrap().str(), 0)), + static_cast( + static_cast(from_string(m.unwrap().str(), 0)-1)), + static_cast(from_string(d.unwrap().str(), 0))), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_date: ", + {{source_location(loc), "the next token is not a local_date"}})); + } +} + +inline result, std::string> +parse_local_time(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto h = lex_time_hour::invoke(inner_loc); + if(!h || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != ':') + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid year format", + {{source_location(inner_loc), "should be `:`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto m = lex_time_minute::invoke(inner_loc); + if(!m || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != ':') + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid month format", + {{source_location(inner_loc), "should be `:`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto s = lex_time_second::invoke(inner_loc); + if(!s) + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid second format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + local_time time( + from_string(h.unwrap().str(), 0), + from_string(m.unwrap().str(), 0), + from_string(s.unwrap().str(), 0), 0, 0); + + const auto before_secfrac = inner_loc.iter(); + if(const auto secfrac = lex_time_secfrac::invoke(inner_loc)) + { + auto sf = secfrac.unwrap().str(); + sf.erase(sf.begin()); // sf.front() == '.' + switch(sf.size() % 3) + { + case 2: sf += '0'; break; + case 1: sf += "00"; break; + case 0: break; + default: break; + } + if(sf.size() >= 9) + { + time.millisecond = from_string(sf.substr(0, 3), 0u); + time.microsecond = from_string(sf.substr(3, 3), 0u); + time.nanosecond = from_string(sf.substr(6, 3), 0u); + } + else if(sf.size() >= 6) + { + time.millisecond = from_string(sf.substr(0, 3), 0u); + time.microsecond = from_string(sf.substr(3, 3), 0u); + } + else if(sf.size() >= 3) + { + time.millisecond = from_string(sf, 0u); + time.microsecond = 0u; + } + } + else + { + if(before_secfrac != inner_loc.iter()) + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid subsecond format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + return ok(std::make_pair(time, token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_time: ", + {{source_location(loc), "the next token is not a local_time"}})); + } +} + +inline result, std::string> +parse_local_datetime(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_date_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + const auto date = parse_local_date(inner_loc); + if(!date || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "date, not datetime"}}), + source_location(inner_loc)); + } + const char delim = *(inner_loc.iter()); + if(delim != 'T' && delim != 't' && delim != ' ') + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "should be `T` or ` ` (space)"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto time = parse_local_time(inner_loc); + if(!time) + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "invalid time fomrat"}}), + source_location(inner_loc)); + } + return ok(std::make_pair( + local_datetime(date.unwrap().first, time.unwrap().first), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_datetime: ", + {{source_location(loc), "the next token is not a local_datetime"}})); + } +} + +inline result, std::string> +parse_offset_datetime(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_offset_date_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + const auto datetime = parse_local_datetime(inner_loc); + if(!datetime || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_offset_datetime: invalid datetime format", + {{source_location(inner_loc), "date, not datetime"}}), + source_location(inner_loc)); + } + time_offset offset(0, 0); + if(const auto ofs = lex_time_numoffset::invoke(inner_loc)) + { + const auto str = ofs.unwrap().str(); + if(str.front() == '+') + { + offset = time_offset(from_string(str.substr(1,2), 0), + from_string(str.substr(4,2), 0)); + } + else + { + offset = time_offset(-from_string(str.substr(1,2), 0), + -from_string(str.substr(4,2), 0)); + } + } + else if(*inner_loc.iter() != 'Z' && *inner_loc.iter() != 'z') + { + throw internal_error(format_underline( + "toml::parse_offset_datetime: invalid datetime format", + {{source_location(inner_loc), "should be `Z` or `+HH:MM`"}}), + source_location(inner_loc)); + } + return ok(std::make_pair(offset_datetime(datetime.unwrap().first, offset), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_offset_datetime: ", + {{source_location(loc), "the next token is not a offset_datetime"}})); + } +} + +inline result, std::string> +parse_simple_key(location& loc) +{ + if(const auto bstr = parse_basic_string(loc)) + { + return ok(std::make_pair(bstr.unwrap().first.str, bstr.unwrap().second)); + } + if(const auto lstr = parse_literal_string(loc)) + { + return ok(std::make_pair(lstr.unwrap().first.str, lstr.unwrap().second)); + } + if(const auto bare = lex_unquoted_key::invoke(loc)) + { + const auto reg = bare.unwrap(); + return ok(std::make_pair(reg.str(), reg)); + } + return err(format_underline("toml::parse_simple_key: ", + {{source_location(loc), "the next token is not a simple key"}})); +} + +// dotted key become vector of keys +inline result, region>, std::string> +parse_key(location& loc) +{ + const auto first = loc.iter(); + // dotted key -> `foo.bar.baz` where several single keys are chained by + // dots. Whitespaces between keys and dots are allowed. + if(const auto token = lex_dotted_key::invoke(loc)) + { + const auto reg = token.unwrap(); + location inner_loc(loc.name(), reg.str()); + std::vector keys; + + while(inner_loc.iter() != inner_loc.end()) + { + lex_ws::invoke(inner_loc); + if(const auto k = parse_simple_key(inner_loc)) + { + keys.push_back(k.unwrap().first); + } + else + { + throw internal_error(format_underline( + "toml::detail::parse_key: dotted key contains invalid key", + {{source_location(inner_loc), k.unwrap_err()}}), + source_location(inner_loc)); + } + + lex_ws::invoke(inner_loc); + if(inner_loc.iter() == inner_loc.end()) + { + break; + } + else if(*inner_loc.iter() == '.') + { + inner_loc.advance(); // to skip `.` + } + else + { + throw internal_error(format_underline("toml::parse_key: " + "dotted key contains invalid key ", + {{source_location(inner_loc), "should be `.`"}}), + source_location(inner_loc)); + } + } + return ok(std::make_pair(keys, reg)); + } + loc.reset(first); + + // simple_key: a single (basic_string|literal_string|bare key) + if(const auto smpl = parse_simple_key(loc)) + { + return ok(std::make_pair(std::vector(1, smpl.unwrap().first), + smpl.unwrap().second)); + } + return err(format_underline("toml::parse_key: an invalid key appeared.", + {{source_location(loc), "is not a valid key"}}, { + "bare keys : non-empty strings composed only of [A-Za-z0-9_-].", + "quoted keys: same as \"basic strings\" or 'literal strings'.", + "dotted keys: sequence of bare or quoted keys joined with a dot." + })); +} + +// forward-decl to implement parse_array and parse_table +template +result parse_value(location&); + +template +result, std::string> +parse_array(location& loc) +{ + using value_type = Value; + using array_type = typename value_type::array_type; + + const auto first = loc.iter(); + if(loc.iter() == loc.end()) + { + return err("toml::parse_array: input is empty"); + } + if(*loc.iter() != '[') + { + return err("toml::parse_array: token is not an array"); + } + loc.advance(); + + using lex_ws_comment_newline = repeat< + either, unlimited>; + + array_type retval; + while(loc.iter() != loc.end()) + { + lex_ws_comment_newline::invoke(loc); // skip + + if(loc.iter() != loc.end() && *loc.iter() == ']') + { + loc.advance(); // skip ']' + return ok(std::make_pair(retval, + region(loc, first, loc.iter()))); + } + + if(auto val = parse_value(loc)) + { + // After TOML v1.0.0-rc.1, array becomes to be able to have values + // with different types. So here we will omit this by default. + // + // But some of the test-suite checks if the parser accepts a hetero- + // geneous arrays, so we keep this for a while. +#ifdef TOML11_DISALLOW_HETEROGENEOUS_ARRAYS + if(!retval.empty() && retval.front().type() != val.as_ok().type()) + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array: " + "type of elements should be the same each other.", { + {source_location(array_start_loc), "array starts here"}, + { + retval.front().location(), + "value has type " + stringize(retval.front().type()) + }, + { + val.unwrap().location(), + "value has different type, " + stringize(val.unwrap().type()) + } + }), source_location(loc)); + } +#endif + retval.push_back(std::move(val.unwrap())); + } + else + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array: " + "value having invalid format appeared in an array", { + {source_location(array_start_loc), "array starts here"}, + {source_location(loc), "it is not a valid value."} + }), source_location(loc)); + } + + using lex_array_separator = sequence, character<','>>; + const auto sp = lex_array_separator::invoke(loc); + if(!sp) + { + lex_ws_comment_newline::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == ']') + { + loc.advance(); // skip ']' + return ok(std::make_pair(retval, + region(loc, first, loc.iter()))); + } + else + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array:" + " missing array separator `,` after a value", { + {source_location(array_start_loc), "array starts here"}, + {source_location(loc), "should be `,`"} + }), source_location(loc)); + } + } + } + loc.reset(first); + throw syntax_error(format_underline("toml::parse_array: " + "array did not closed by `]`", + {{source_location(loc), "should be closed"}}), + source_location(loc)); +} + +template +result, region>, Value>, std::string> +parse_key_value_pair(location& loc) +{ + using value_type = Value; + + const auto first = loc.iter(); + auto key_reg = parse_key(loc); + if(!key_reg) + { + std::string msg = std::move(key_reg.unwrap_err()); + // if the next token is keyvalue-separator, it means that there are no + // key. then we need to show error as "empty key is not allowed". + if(const auto keyval_sep = lex_keyval_sep::invoke(loc)) + { + loc.reset(first); + msg = format_underline("toml::parse_key_value_pair: " + "empty key is not allowed.", + {{source_location(loc), "key expected before '='"}}); + } + return err(std::move(msg)); + } + + const auto kvsp = lex_keyval_sep::invoke(loc); + if(!kvsp) + { + std::string msg; + // if the line contains '=' after the invalid sequence, possibly the + // error is in the key (like, invalid character in bare key). + const auto line_end = std::find(loc.iter(), loc.end(), '\n'); + if(std::find(loc.iter(), line_end, '=') != line_end) + { + msg = format_underline("toml::parse_key_value_pair: " + "invalid format for key", + {{source_location(loc), "invalid character in key"}}, + {"Did you forget '.' to separate dotted-key?", + "Allowed characters for bare key are [0-9a-zA-Z_-]."}); + } + else // if not, the error is lack of key-value separator. + { + msg = format_underline("toml::parse_key_value_pair: " + "missing key-value separator `=`", + {{source_location(loc), "should be `=`"}}); + } + loc.reset(first); + return err(std::move(msg)); + } + + const auto after_kvsp = loc.iter(); // err msg + auto val = parse_value(loc); + if(!val) + { + std::string msg; + loc.reset(after_kvsp); + // check there is something not a comment/whitespace after `=` + if(sequence, maybe, lex_newline>::invoke(loc)) + { + loc.reset(after_kvsp); + msg = format_underline("toml::parse_key_value_pair: " + "missing value after key-value separator '='", + {{source_location(loc), "expected value, but got nothing"}}); + } + else // there is something not a comment/whitespace, so invalid format. + { + msg = std::move(val.unwrap_err()); + } + loc.reset(first); + return err(msg); + } + return ok(std::make_pair(std::move(key_reg.unwrap()), + std::move(val.unwrap()))); +} + +// for error messages. +template +std::string format_dotted_keys(InputIterator first, const InputIterator last) +{ + static_assert(std::is_same::value_type>::value,""); + + std::string retval(*first++); + for(; first != last; ++first) + { + retval += '.'; + retval += *first; + } + return retval; +} + +// forward decl for is_valid_forward_table_definition +result, region>, std::string> +parse_table_key(location& loc); + +// The following toml file is allowed. +// ```toml +// [a.b.c] # here, table `a` has element `b`. +// foo = "bar" +// [a] # merge a = {baz = "qux"} to a = {b = {...}} +// baz = "qux" +// ``` +// But the following is not allowed. +// ```toml +// [a] +// b.c.foo = "bar" +// [a] # error! the same table [a] defined! +// baz = "qux" +// ``` +// The following is neither allowed. +// ```toml +// a = { b.c.foo = "bar"} +// [a] # error! the same table [a] defined! +// baz = "qux" +// ``` +// Here, it parses region of `tab->at(k)` as a table key and check the depth +// of the key. If the key region points deeper node, it would be allowed. +// Otherwise, the key points the same node. It would be rejected. +template +bool is_valid_forward_table_definition(const Value& fwd, + Iterator key_first, Iterator key_curr, Iterator key_last) +{ + std::string internal = ""; + if(const auto ptr = detail::get_region(fwd)) + { + internal = ptr->str(); + } + location def("internal", std::move(internal)); + if(const auto tabkeys = parse_table_key(def)) + { + // table keys always contains all the nodes from the root. + const auto& tks = tabkeys.unwrap().first; + if(std::size_t(std::distance(key_first, key_last)) == tks.size() && + std::equal(tks.begin(), tks.end(), key_first)) + { + // the keys are equivalent. it is not allowed. + return false; + } + // the keys are not equivalent. it is allowed. + return true; + } + if(const auto dotkeys = parse_key(def)) + { + // consider the following case. + // [a] + // b.c = {d = 42} + // [a.b.c] + // e = 2.71 + // this defines the table [a.b.c] twice. no? + + // a dotted key starts from the node representing a table in which the + // dotted key belongs to. + const auto& dks = dotkeys.unwrap().first; + if(std::size_t(std::distance(key_curr, key_last)) == dks.size() && + std::equal(dks.begin(), dks.end(), key_curr)) + { + // the keys are equivalent. it is not allowed. + return false; + } + // the keys are not equivalent. it is allowed. + return true; + } + return false; +} + +template +result +insert_nested_key(typename Value::table_type& root, const Value& v, + InputIterator iter, const InputIterator last, + region key_reg, + const bool is_array_of_table = false) +{ + static_assert(std::is_same::value_type>::value,""); + + using value_type = Value; + using table_type = typename value_type::table_type; + using array_type = typename value_type::array_type; + + const auto first = iter; + assert(iter != last); + + table_type* tab = std::addressof(root); + for(; iter != last; ++iter) // search recursively + { + const key& k = *iter; + if(std::next(iter) == last) // k is the last key + { + // XXX if the value is array-of-tables, there can be several + // tables that are in the same array. in that case, we need to + // find the last element and insert it to there. + if(is_array_of_table) + { + if(tab->count(k) == 1) // there is already an array of table + { + if(tab->at(k).is_table()) + { + // show special err msg for conflicting table + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), + "\") cannot be defined"), { + {tab->at(k).location(), "table already defined"}, + {v.location(), "this conflicts with the previous table"} + }), v.location()); + } + else if(!(tab->at(k).is_array())) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides with" + " existing value"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value already exists")}, + {v.location(), + "while inserting this array-of-tables"} + }), v.location()); + } + // the above if-else-if checks tab->at(k) is an array + auto& a = tab->at(k).as_array(); + if(!(a.front().is_table())) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides with" + " existing value"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value already exists")}, + {v.location(), + "while inserting this array-of-tables"} + }), v.location()); + } + // avoid conflicting array of table like the following. + // ```toml + // a = [{b = 42}] # define a as an array of *inline* tables + // [[a]] # a is an array of *multi-line* tables + // b = 54 + // ``` + // Here, from the type information, these cannot be detected + // bacause inline table is also a table. + // But toml v0.5.0 explicitly says it is invalid. The above + // array-of-tables has a static size and appending to the + // array is invalid. + // In this library, multi-line table value has a region + // that points to the key of the table (e.g. [[a]]). By + // comparing the first two letters in key, we can detect + // the array-of-table is inline or multiline. + if(const auto ptr = detail::get_region(a.front())) + { + if(ptr->str().substr(0,2) != "[[") + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides " + "with existing array-of-tables"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value has static size")}, + {v.location(), + "appending it to the statically sized array"} + }), v.location()); + } + } + a.push_back(v); + return ok(true); + } + else // if not, we need to create the array of table + { + // XXX: Consider the following array of tables. + // ```toml + // # This is a comment. + // [[aot]] + // foo = "bar" + // ``` + // Here, the comment is for `aot`. But here, actually two + // values are defined. An array that contains tables, named + // `aot`, and the 0th element of the `aot`, `{foo = "bar"}`. + // Those two are different from each other. But both of them + // points to the same portion of the TOML file, `[[aot]]`, + // so `key_reg.comments()` returns `# This is a comment`. + // If it is assigned as a comment of `aot` defined here, the + // comment will be duplicated. Both the `aot` itself and + // the 0-th element will have the same comment. This causes + // "duplication of the same comments" bug when the data is + // serialized. + // Next, consider the following. + // ```toml + // # comment 1 + // aot = [ + // # coment 2 + // {foo = "bar"}, + // ] + // ``` + // In this case, we can distinguish those two comments. So + // here we need to add "comment 1" to the `aot` and + // "comment 2" to the 0th element of that. + // To distinguish those two, we check the key region. + std::vector comments{/* empty by default */}; + if(key_reg.str().substr(0, 2) != "[[") + { + comments = key_reg.comments(); + } + value_type aot(array_type(1, v), key_reg, std::move(comments)); + tab->insert(std::make_pair(k, aot)); + return ok(true); + } + } // end if(array of table) + + if(tab->count(k) == 1) + { + if(tab->at(k).is_table() && v.is_table()) + { + if(!is_valid_forward_table_definition( + tab->at(k), first, iter, last)) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: table (\"", + format_dotted_keys(first, last), + "\") already exists."), { + {tab->at(k).location(), "table already exists here"}, + {v.location(), "table defined twice"} + }), v.location()); + } + // to allow the following toml file. + // [a.b.c] + // d = 42 + // [a] + // e = 2.71 + auto& t = tab->at(k).as_table(); + for(const auto& kv : v.as_table()) + { + t[kv.first] = kv.second; + } + detail::change_region(tab->at(k), key_reg); + return ok(true); + } + else if(v.is_table() && + tab->at(k).is_array() && + tab->at(k).as_array().size() > 0 && + tab->at(k).as_array().front().is_table()) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of tables (\"", + format_dotted_keys(first, last), "\") already exists."), { + {tab->at(k).location(), "array of tables defined here"}, + {v.location(), "table conflicts with the previous array of table"} + }), v.location()); + } + else + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: value (\"", + format_dotted_keys(first, last), "\") already exists."), { + {tab->at(k).location(), "value already exists here"}, + {v.location(), "value defined twice"} + }), v.location()); + } + } + tab->insert(std::make_pair(k, v)); + return ok(true); + } + else // k is not the last one, we should insert recursively + { + // if there is no corresponding value, insert it first. + // related: you don't need to write + // # [x] + // # [x.y] + // to write + // [x.y.z] + if(tab->count(k) == 0) + { + // a table that is defined implicitly doesn't have any comments. + (*tab)[k] = value_type(table_type{}, key_reg, {/*no comment*/}); + } + + // type checking... + if(tab->at(k).is_table()) + { + // According to toml-lang/toml:36d3091b3 "Clarify that inline + // tables are immutable", check if it adds key-value pair to an + // inline table. + if(const auto* ptr = get_region(tab->at(k))) + { + // here, if the value is a (multi-line) table, the region + // should be something like `[table-name]`. + if(ptr->front() == '{') + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: inserting to an inline table (", + format_dotted_keys(first, std::next(iter)), + ") but inline tables are immutable"), { + {tab->at(k).location(), "inline tables are immutable"}, + {v.location(), "inserting this"} + }), v.location()); + } + } + tab = std::addressof((*tab)[k].as_table()); + } + else if(tab->at(k).is_array()) // inserting to array-of-tables? + { + auto& a = (*tab)[k].as_array(); + if(!a.back().is_table()) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: target (", + format_dotted_keys(first, std::next(iter)), + ") is neither table nor an array of tables"), { + {a.back().location(), concat_to_string( + "actual type is ", a.back().type())}, + {v.location(), "inserting this"} + }), v.location()); + } + tab = std::addressof(a.back().as_table()); + } + else + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: target (", + format_dotted_keys(first, std::next(iter)), + ") is neither table nor an array of tables"), { + {tab->at(k).location(), concat_to_string( + "actual type is ", tab->at(k).type())}, + {v.location(), "inserting this"} + }), v.location()); + } + } + } + return err(std::string("toml::detail::insert_nested_key: never reach here")); +} + +template +result, std::string> +parse_inline_table(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + table_type retval; + if(!(loc.iter() != loc.end() && *loc.iter() == '{')) + { + return err(format_underline("toml::parse_inline_table: ", + {{source_location(loc), "the next token is not an inline table"}})); + } + loc.advance(); + // it starts from "{". it should be formatted as inline-table + while(loc.iter() != loc.end()) + { + maybe::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == '}') + { + loc.advance(); // skip `}` + return ok(std::make_pair(retval, + region(loc, first, loc.iter()))); + } + + const auto kv_r = parse_key_value_pair(loc); + if(!kv_r) + { + return err(kv_r.unwrap_err()); + } + const auto& kvpair = kv_r.unwrap(); + const std::vector& keys = kvpair.first.first; + const auto& key_reg = kvpair.first.second; + const value_type& val = kvpair.second; + + const auto inserted = + insert_nested_key(retval, val, keys.begin(), keys.end(), key_reg); + if(!inserted) + { + throw internal_error("toml::parse_inline_table: " + "failed to insert value into table: " + inserted.unwrap_err(), + source_location(loc)); + } + + using lex_table_separator = sequence, character<','>>; + const auto sp = lex_table_separator::invoke(loc); + if(!sp) + { + maybe::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == '}') + { + loc.advance(); // skip `}` + return ok(std::make_pair( + retval, region(loc, first, loc.iter()))); + } + else if(*loc.iter() == '#' || *loc.iter() == '\r' || *loc.iter() == '\n') + { + throw syntax_error(format_underline( + "toml::parse_inline_table: missing curly brace `}`", + {{source_location(loc), "should be `}`"}}), + source_location(loc)); + } + else + { + throw syntax_error(format_underline( + "toml::parse_inline_table: missing table separator `,` ", + {{source_location(loc), "should be `,`"}}), + source_location(loc)); + } + } + } + loc.reset(first); + throw syntax_error(format_underline("toml::parse_inline_table: " + "inline table did not closed by `}`", + {{source_location(loc), "should be closed"}}), + source_location(loc)); +} + +inline result guess_number_type(const location& l) +{ + // This function tries to find some (common) mistakes by checking characters + // that follows the last character of a value. But it is often difficult + // because some non-newline characters can appear after a value. E.g. + // spaces, tabs, commas (in an array or inline table), closing brackets + // (of an array or inline table), comment-sign (#). Since this function + // does not parse further, those characters are always allowed to be there. + location loc = l; + + if(lex_offset_date_time::invoke(loc)) {return ok(value_t::offset_datetime);} + loc.reset(l.iter()); + + if(lex_local_date_time::invoke(loc)) + { + // bad offset may appear after this. + if(loc.iter() != loc.end() && (*loc.iter() == '+' || *loc.iter() == '-' + || *loc.iter() == 'Z' || *loc.iter() == 'z')) + { + return err(format_underline("bad offset: should be [+-]HH:MM or Z", + {{source_location(loc), "[+-]HH:MM or Z"}}, + {"pass: +09:00, -05:30", "fail: +9:00, -5:30"})); + } + return ok(value_t::local_datetime); + } + loc.reset(l.iter()); + + if(lex_local_date::invoke(loc)) + { + // bad time may appear after this. + // A space is allowed as a delimiter between local time. But there are + // both cases in which a space becomes valid or invalid. + // - invalid: 2019-06-16 7:00:00 + // - valid : 2019-06-16 07:00:00 + if(loc.iter() != loc.end()) + { + const auto c = *loc.iter(); + if(c == 'T' || c == 't') + { + return err(format_underline("bad time: should be HH:MM:SS.subsec", + {{source_location(loc), "HH:MM:SS.subsec"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 17:32"})); + } + if('0' <= c && c <= '9') + { + return err(format_underline("bad time: missing T", + {{source_location(loc), "T or space required here"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + if(c == ' ' && std::next(loc.iter()) != loc.end() && + ('0' <= *std::next(loc.iter()) && *std::next(loc.iter())<= '9')) + { + loc.advance(); + return err(format_underline("bad time: should be HH:MM:SS.subsec", + {{source_location(loc), "HH:MM:SS.subsec"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + } + return ok(value_t::local_date); + } + loc.reset(l.iter()); + + if(lex_local_time::invoke(loc)) {return ok(value_t::local_time);} + loc.reset(l.iter()); + + if(lex_float::invoke(loc)) + { + if(loc.iter() != loc.end() && *loc.iter() == '_') + { + return err(format_underline("bad float: `_` should be surrounded by digits", + {{source_location(loc), "here"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + return ok(value_t::floating); + } + loc.reset(l.iter()); + + if(lex_integer::invoke(loc)) + { + if(loc.iter() != loc.end()) + { + const auto c = *loc.iter(); + if(c == '_') + { + return err(format_underline("bad integer: `_` should be surrounded by digits", + {{source_location(loc), "here"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + if('0' <= c && c <= '9') + { + // leading zero. point '0' + loc.retrace(); + return err(format_underline("bad integer: leading zero", + {{source_location(loc), "here"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + if(c == ':' || c == '-') + { + return err(format_underline("bad datetime: invalid format", + {{source_location(loc), "here"}}, + {"pass: 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z", + "fail: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30"})); + } + if(c == '.' || c == 'e' || c == 'E') + { + return err(format_underline("bad float: invalid format", + {{source_location(loc), "here"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + } + return ok(value_t::integer); + } + if(loc.iter() != loc.end() && *loc.iter() == '.') + { + return err(format_underline("bad float: invalid format", + {{source_location(loc), "integer part required before this"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + if(loc.iter() != loc.end() && *loc.iter() == '_') + { + return err(format_underline("bad number: `_` should be surrounded by digits", + {{source_location(loc), "`_` is not surrounded by digits"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + return err(format_underline("bad format: unknown value appeared", + {{source_location(loc), "here"}})); +} + +inline result guess_value_type(const location& loc) +{ + switch(*loc.iter()) + { + case '"' : {return ok(value_t::string); } + case '\'': {return ok(value_t::string); } + case 't' : {return ok(value_t::boolean); } + case 'f' : {return ok(value_t::boolean); } + case '[' : {return ok(value_t::array); } + case '{' : {return ok(value_t::table); } + case 'i' : {return ok(value_t::floating);} // inf. + case 'n' : {return ok(value_t::floating);} // nan. + default : {return guess_number_type(loc);} + } +} + +template +result +parse_value_helper(result, std::string> rslt) +{ + if(rslt.is_ok()) + { + auto comments = rslt.as_ok().second.comments(); + return ok(Value(std::move(rslt.as_ok()), std::move(comments))); + } + else + { + return err(std::move(rslt.as_err())); + } +} + +template +result parse_value(location& loc) +{ + const auto first = loc.iter(); + if(first == loc.end()) + { + return err(format_underline("toml::parse_value: input is empty", + {{source_location(loc), ""}})); + } + + const auto type = guess_value_type(loc); + if(!type) + { + return err(type.unwrap_err()); + } + + switch(type.unwrap()) + { + case value_t::boolean : {return parse_value_helper(parse_boolean(loc) );} + case value_t::integer : {return parse_value_helper(parse_integer(loc) );} + case value_t::floating : {return parse_value_helper(parse_floating(loc) );} + case value_t::string : {return parse_value_helper(parse_string(loc) );} + case value_t::offset_datetime: {return parse_value_helper(parse_offset_datetime(loc) );} + case value_t::local_datetime : {return parse_value_helper(parse_local_datetime(loc) );} + case value_t::local_date : {return parse_value_helper(parse_local_date(loc) );} + case value_t::local_time : {return parse_value_helper(parse_local_time(loc) );} + case value_t::array : {return parse_value_helper(parse_array(loc) );} + case value_t::table : {return parse_value_helper(parse_inline_table(loc));} + default: + { + const auto msg = format_underline("toml::parse_value: " + "unknown token appeared", {{source_location(loc), "unknown"}}); + loc.reset(first); + return err(msg); + } + } +} + +inline result, region>, std::string> +parse_table_key(location& loc) +{ + if(auto token = lex_std_table::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_std_table_open::invoke(inner_loc); + if(!open || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `[`", + {{source_location(inner_loc), "should be `[`"}}), + source_location(inner_loc)); + } + // to skip [ a . b . c ] + // ^----------- this whitespace + lex_ws::invoke(inner_loc); + const auto keys = parse_key(inner_loc); + if(!keys) + { + throw internal_error(format_underline( + "toml::parse_table_key: invalid key", + {{source_location(inner_loc), "not key"}}), + source_location(inner_loc)); + } + // to skip [ a . b . c ] + // ^-- this whitespace + lex_ws::invoke(inner_loc); + const auto close = lex_std_table_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `]`", + {{source_location(inner_loc), "should be `]`"}}), + source_location(inner_loc)); + } + + // after [table.key], newline or EOF(empty table) requried. + if(loc.iter() != loc.end()) + { + using lex_newline_after_table_key = + sequence, maybe, lex_newline>; + const auto nl = lex_newline_after_table_key::invoke(loc); + if(!nl) + { + throw syntax_error(format_underline( + "toml::parse_table_key: newline required after [table.key]", + {{source_location(loc), "expected newline"}}), + source_location(loc)); + } + } + return ok(std::make_pair(keys.unwrap().first, token.unwrap())); + } + else + { + return err(format_underline("toml::parse_table_key: " + "not a valid table key", {{source_location(loc), "here"}})); + } +} + +inline result, region>, std::string> +parse_array_table_key(location& loc) +{ + if(auto token = lex_array_table::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_array_table_open::invoke(inner_loc); + if(!open || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_array_table_key: no `[[`", + {{source_location(inner_loc), "should be `[[`"}}), + source_location(inner_loc)); + } + lex_ws::invoke(inner_loc); + const auto keys = parse_key(inner_loc); + if(!keys) + { + throw internal_error(format_underline( + "toml::parse_array_table_key: invalid key", + {{source_location(inner_loc), "not a key"}}), + source_location(inner_loc)); + } + lex_ws::invoke(inner_loc); + const auto close = lex_array_table_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `]]`", + {{source_location(inner_loc), "should be `]]`"}}), + source_location(inner_loc)); + } + + // after [[table.key]], newline or EOF(empty table) requried. + if(loc.iter() != loc.end()) + { + using lex_newline_after_table_key = + sequence, maybe, lex_newline>; + const auto nl = lex_newline_after_table_key::invoke(loc); + if(!nl) + { + throw syntax_error(format_underline("toml::" + "parse_array_table_key: newline required after [[table.key]]", + {{source_location(loc), "expected newline"}}), + source_location(loc)); + } + } + return ok(std::make_pair(keys.unwrap().first, token.unwrap())); + } + else + { + return err(format_underline("toml::parse_array_table_key: " + "not a valid table key", {{source_location(loc), "here"}})); + } +} + +// parse table body (key-value pairs until the iter hits the next [tablekey]) +template +result +parse_ml_table(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + if(first == loc.end()) + { + return ok(table_type{}); + } + + // XXX at lest one newline is needed. + using skip_line = repeat< + sequence, maybe, lex_newline>, at_least<1>>; + skip_line::invoke(loc); + lex_ws::invoke(loc); + + table_type tab; + while(loc.iter() != loc.end()) + { + lex_ws::invoke(loc); + const auto before = loc.iter(); + if(const auto tmp = parse_array_table_key(loc)) // next table found + { + loc.reset(before); + return ok(tab); + } + if(const auto tmp = parse_table_key(loc)) // next table found + { + loc.reset(before); + return ok(tab); + } + + if(const auto kv = parse_key_value_pair(loc)) + { + const auto& kvpair = kv.unwrap(); + const std::vector& keys = kvpair.first.first; + const auto& key_reg = kvpair.first.second; + const value_type& val = kvpair.second; + const auto inserted = + insert_nested_key(tab, val, keys.begin(), keys.end(), key_reg); + if(!inserted) + { + return err(inserted.unwrap_err()); + } + } + else + { + return err(kv.unwrap_err()); + } + + // comment lines are skipped by the above function call. + // However, since the `skip_line` requires at least 1 newline, it fails + // if the file ends with ws and/or comment without newline. + // `skip_line` matches `ws? + comment? + newline`, not `ws` or `comment` + // itself. To skip the last ws and/or comment, call lexers. + // It does not matter if these fails, so the return value is discarded. + lex_ws::invoke(loc); + lex_comment::invoke(loc); + + // skip_line is (whitespace? comment? newline)_{1,}. multiple empty lines + // and comments after the last key-value pairs are allowed. + const auto newline = skip_line::invoke(loc); + if(!newline && loc.iter() != loc.end()) + { + const auto before2 = loc.iter(); + lex_ws::invoke(loc); // skip whitespace + const auto msg = format_underline("toml::parse_table: " + "invalid line format", {{source_location(loc), concat_to_string( + "expected newline, but got '", show_char(*loc.iter()), "'.")}}); + loc.reset(before2); + return err(msg); + } + + // the skip_lines only matches with lines that includes newline. + // to skip the last line that includes comment and/or whitespace + // but no newline, call them one more time. + lex_ws::invoke(loc); + lex_comment::invoke(loc); + } + return ok(tab); +} + +template +result parse_toml_file(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + if(first == loc.end()) + { + return ok(value_type(table_type{} /*, empty file has no region ...*/)); + } + + // put the first line as a region of a file + // Here first != loc.end(), so taking std::next is okay + const region file(loc, first, std::next(loc.iter())); + + // The first successive comments that are separated from the first value + // by an empty line are for a file itself. + // ```toml + // # this is a comment for a file. + // + // key = "the first value" + // ``` + // ```toml + // # this is a comment for "the first value". + // key = "the first value" + // ``` + std::vector comments; + using lex_first_comments = sequence< + repeat, lex_comment, lex_newline>, at_least<1>>, + sequence, lex_newline> + >; + if(const auto token = lex_first_comments::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + while(inner_loc.iter() != inner_loc.end()) + { + maybe::invoke(inner_loc); // remove ws if exists + if(lex_newline::invoke(inner_loc)) + { + assert(inner_loc.iter() == inner_loc.end()); + break; // empty line found. + } + auto com = lex_comment::invoke(inner_loc).unwrap().str(); + com.erase(com.begin()); // remove # sign + comments.push_back(std::move(com)); + lex_newline::invoke(inner_loc); + } + } + + table_type data; + // root object is also a table, but without [tablename] + if(auto tab = parse_ml_table(loc)) + { + data = std::move(tab.unwrap()); + } + else // failed (empty table is regarded as success in parse_ml_table) + { + return err(tab.unwrap_err()); + } + while(loc.iter() != loc.end()) + { + // here, the region of [table] is regarded as the table-key because + // the table body is normally too big and it is not so informative + // if the first key-value pair of the table is shown in the error + // message. + if(const auto tabkey = parse_array_table_key(loc)) + { + const auto tab = parse_ml_table(loc); + if(!tab){return err(tab.unwrap_err());} + + const auto& tk = tabkey.unwrap(); + const auto& keys = tk.first; + const auto& reg = tk.second; + + const auto inserted = insert_nested_key(data, + value_type(tab.unwrap(), reg, reg.comments()), + keys.begin(), keys.end(), reg, + /*is_array_of_table=*/ true); + if(!inserted) {return err(inserted.unwrap_err());} + + continue; + } + if(const auto tabkey = parse_table_key(loc)) + { + const auto tab = parse_ml_table(loc); + if(!tab){return err(tab.unwrap_err());} + + const auto& tk = tabkey.unwrap(); + const auto& keys = tk.first; + const auto& reg = tk.second; + + const auto inserted = insert_nested_key(data, + value_type(tab.unwrap(), reg, reg.comments()), + keys.begin(), keys.end(), reg); + if(!inserted) {return err(inserted.unwrap_err());} + + continue; + } + return err(format_underline("toml::parse_toml_file: " + "unknown line appeared", {{source_location(loc), "unknown format"}})); + } + + return ok(Value(std::move(data), file, comments)); +} + +} // detail + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value +parse(std::istream& is, const std::string& fname = "unknown file") +{ + using value_type = basic_value; + + const auto beg = is.tellg(); + is.seekg(0, std::ios::end); + const auto end = is.tellg(); + const auto fsize = end - beg; + is.seekg(beg); + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + is.read(letters.data(), fsize); + + while(!letters.empty() && letters.back() == '\0') + { + letters.pop_back(); + } + assert(letters.empty() || letters.back() != '\0'); + + detail::location loc(std::move(fname), std::move(letters)); + + // skip BOM if exists. + // XXX component of BOM (like 0xEF) exceeds the representable range of + // signed char, so on some (actually, most) of the environment, these cannot + // be compared to char. However, since we are always out of luck, we need to + // check our chars are equivalent to BOM. To do this, first we need to + // convert char to unsigned char to guarantee the comparability. + if(loc.source()->size() >= 3) + { + std::array BOM; + std::memcpy(BOM.data(), loc.source()->data(), 3); + if(BOM[0] == 0xEF && BOM[1] == 0xBB && BOM[2] == 0xBF) + { + loc.advance(3); // BOM found. skip. + } + } + + const auto data = detail::parse_toml_file(loc); + if(!data) + { + throw syntax_error(data.unwrap_err(), source_location(loc)); + } + return data.unwrap(); +} + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const std::string& fname) +{ + std::ifstream ifs(fname.c_str(), std::ios_base::binary); + if(!ifs.good()) + { + throw std::runtime_error("toml::parse: file open error -> " + fname); + } + return parse(ifs, fname); +} + +#ifdef TOML11_HAS_STD_FILESYSTEM +// This function just forwards `parse("filename.toml")` to std::string version +// to avoid the ambiguity in overload resolution. +// +// Both std::string and std::filesystem::path are convertible from const char*. +// Without this, both parse(std::string) and parse(std::filesystem::path) +// matches to parse("filename.toml"). This breaks the existing code. +// +// This function exactly matches to the invokation with c-string. +// So this function is preferred than others and the ambiguity disappears. +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const char* fname) +{ + return parse(std::string(fname)); +} + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const std::filesystem::path& fpath) +{ + std::ifstream ifs(fpath, std::ios_base::binary); + if(!ifs.good()) + { + throw std::runtime_error("toml::parse: file open error -> " + + fpath.string()); + } + return parse(ifs, fpath.string()); +} +#endif // TOML11_HAS_STD_FILESYSTEM + +} // toml +#endif// TOML11_PARSER_HPP diff --git a/libraries/toml11/include/toml/region.hpp b/libraries/toml11/include/toml/region.hpp new file mode 100644 index 000000000..37ba3a305 --- /dev/null +++ b/libraries/toml11/include/toml/region.hpp @@ -0,0 +1,417 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_REGION_HPP +#define TOML11_REGION_HPP +#include +#include +#include +#include +#include +#include +#include +#include "color.hpp" + +namespace toml +{ +namespace detail +{ + +// helper function to avoid std::string(0, 'c') or std::string(iter, iter) +template +std::string make_string(Iterator first, Iterator last) +{ + if(first == last) {return "";} + return std::string(first, last); +} +inline std::string make_string(std::size_t len, char c) +{ + if(len == 0) {return "";} + return std::string(len, c); +} + +// region_base is a base class of location and region that are defined below. +// it will be used to generate better error messages. +struct region_base +{ + region_base() = default; + virtual ~region_base() = default; + region_base(const region_base&) = default; + region_base(region_base&& ) = default; + region_base& operator=(const region_base&) = default; + region_base& operator=(region_base&& ) = default; + + virtual bool is_ok() const noexcept {return false;} + virtual char front() const noexcept {return '\0';} + + virtual std::string str() const {return std::string("unknown region");} + virtual std::string name() const {return std::string("unknown file");} + virtual std::string line() const {return std::string("unknown line");} + virtual std::string line_num() const {return std::string("?");} + + // length of the region + virtual std::size_t size() const noexcept {return 0;} + // number of characters in the line before the region + virtual std::size_t before() const noexcept {return 0;} + // number of characters in the line after the region + virtual std::size_t after() const noexcept {return 0;} + + virtual std::vector comments() const {return {};} + // ```toml + // # comment_before + // key = "value" # comment_inline + // ``` +}; + +// location represents a position in a container, which contains a file content. +// it can be considered as a region that contains only one character. +// +// it contains pointer to the file content and iterator that points the current +// location. +struct location final : public region_base +{ + using const_iterator = typename std::vector::const_iterator; + using difference_type = typename const_iterator::difference_type; + using source_ptr = std::shared_ptr>; + + location(std::string name, std::vector cont) + : source_(std::make_shared>(std::move(cont))), + line_number_(1), source_name_(std::move(name)), iter_(source_->cbegin()) + {} + location(std::string name, const std::string& cont) + : source_(std::make_shared>(cont.begin(), cont.end())), + line_number_(1), source_name_(std::move(name)), iter_(source_->cbegin()) + {} + + location(const location&) = default; + location(location&&) = default; + location& operator=(const location&) = default; + location& operator=(location&&) = default; + ~location() = default; + + bool is_ok() const noexcept override {return static_cast(source_);} + char front() const noexcept override {return *iter_;} + + // this const prohibits codes like `++(loc.iter())`. + const const_iterator iter() const noexcept {return iter_;} + + const_iterator begin() const noexcept {return source_->cbegin();} + const_iterator end() const noexcept {return source_->cend();} + + // XXX `location::line_num()` used to be implemented using `std::count` to + // count a number of '\n'. But with a long toml file (typically, 10k lines), + // it becomes intolerably slow because each time it generates error messages, + // it counts '\n' from thousands of characters. To workaround it, I decided + // to introduce `location::line_number_` member variable and synchronize it + // to the location changes the point to look. So an overload of `iter()` + // which returns mutable reference is removed and `advance()`, `retrace()` + // and `reset()` is added. + void advance(difference_type n = 1) noexcept + { + this->line_number_ += static_cast( + std::count(this->iter_, std::next(this->iter_, n), '\n')); + this->iter_ += n; + return; + } + void retrace(difference_type n = 1) noexcept + { + this->line_number_ -= static_cast( + std::count(std::prev(this->iter_, n), this->iter_, '\n')); + this->iter_ -= n; + return; + } + void reset(const_iterator rollback) noexcept + { + // since c++11, std::distance works in both ways for random-access + // iterators and returns a negative value if `first > last`. + if(0 <= std::distance(rollback, this->iter_)) // rollback < iter + { + this->line_number_ -= static_cast( + std::count(rollback, this->iter_, '\n')); + } + else // iter < rollback [[unlikely]] + { + this->line_number_ += static_cast( + std::count(this->iter_, rollback, '\n')); + } + this->iter_ = rollback; + return; + } + + std::string str() const override {return make_string(1, *this->iter());} + std::string name() const override {return source_name_;} + + std::string line_num() const override + { + return std::to_string(this->line_number_); + } + + std::string line() const override + { + return make_string(this->line_begin(), this->line_end()); + } + + const_iterator line_begin() const noexcept + { + using reverse_iterator = std::reverse_iterator; + return std::find(reverse_iterator(this->iter()), + reverse_iterator(this->begin()), '\n').base(); + } + const_iterator line_end() const noexcept + { + return std::find(this->iter(), this->end(), '\n'); + } + + // location is always points a character. so the size is 1. + std::size_t size() const noexcept override + { + return 1u; + } + std::size_t before() const noexcept override + { + const auto sz = std::distance(this->line_begin(), this->iter()); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t after() const noexcept override + { + const auto sz = std::distance(this->iter(), this->line_end()); + assert(sz >= 0); + return static_cast(sz); + } + + source_ptr const& source() const& noexcept {return source_;} + source_ptr&& source() && noexcept {return std::move(source_);} + + private: + + source_ptr source_; + std::size_t line_number_; + std::string source_name_; + const_iterator iter_; +}; + +// region represents a range in a container, which contains a file content. +// +// it contains pointer to the file content and iterator that points the first +// and last location. +struct region final : public region_base +{ + using const_iterator = typename std::vector::const_iterator; + using source_ptr = std::shared_ptr>; + + // delete default constructor. source_ never be null. + region() = delete; + + explicit region(const location& loc) + : source_(loc.source()), source_name_(loc.name()), + first_(loc.iter()), last_(loc.iter()) + {} + explicit region(location&& loc) + : source_(loc.source()), source_name_(loc.name()), + first_(loc.iter()), last_(loc.iter()) + {} + + region(const location& loc, const_iterator f, const_iterator l) + : source_(loc.source()), source_name_(loc.name()), first_(f), last_(l) + {} + region(location&& loc, const_iterator f, const_iterator l) + : source_(loc.source()), source_name_(loc.name()), first_(f), last_(l) + {} + + region(const region&) = default; + region(region&&) = default; + region& operator=(const region&) = default; + region& operator=(region&&) = default; + ~region() = default; + + region& operator+=(const region& other) + { + // different regions cannot be concatenated + assert(this->begin() == other.begin() && this->end() == other.end() && + this->last_ == other.first_); + + this->last_ = other.last_; + return *this; + } + + bool is_ok() const noexcept override {return static_cast(source_);} + char front() const noexcept override {return *first_;} + + std::string str() const override {return make_string(first_, last_);} + std::string line() const override + { + if(this->contain_newline()) + { + return make_string(this->line_begin(), + std::find(this->line_begin(), this->last(), '\n')); + } + return make_string(this->line_begin(), this->line_end()); + } + std::string line_num() const override + { + return std::to_string(1 + std::count(this->begin(), this->first(), '\n')); + } + + std::size_t size() const noexcept override + { + const auto sz = std::distance(first_, last_); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t before() const noexcept override + { + const auto sz = std::distance(this->line_begin(), this->first()); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t after() const noexcept override + { + const auto sz = std::distance(this->last(), this->line_end()); + assert(sz >= 0); + return static_cast(sz); + } + + bool contain_newline() const noexcept + { + return std::find(this->first(), this->last(), '\n') != this->last(); + } + + const_iterator line_begin() const noexcept + { + using reverse_iterator = std::reverse_iterator; + return std::find(reverse_iterator(this->first()), + reverse_iterator(this->begin()), '\n').base(); + } + const_iterator line_end() const noexcept + { + return std::find(this->last(), this->end(), '\n'); + } + + const_iterator begin() const noexcept {return source_->cbegin();} + const_iterator end() const noexcept {return source_->cend();} + const_iterator first() const noexcept {return first_;} + const_iterator last() const noexcept {return last_;} + + source_ptr const& source() const& noexcept {return source_;} + source_ptr&& source() && noexcept {return std::move(source_);} + + std::string name() const override {return source_name_;} + + std::vector comments() const override + { + // assuming the current region (`*this`) points a value. + // ```toml + // a = "value" + // ^^^^^^^- this region + // ``` + using rev_iter = std::reverse_iterator; + + std::vector com{}; + { + // find comments just before the current region. + // ```toml + // # this should be collected. + // # this also. + // a = value # not this. + // ``` + + // # this is a comment for `a`, not array elements. + // a = [1, 2, 3, 4, 5] + if(this->first() == std::find_if(this->line_begin(), this->first(), + [](const char c) noexcept -> bool {return c == '[' || c == '{';})) + { + auto iter = this->line_begin(); // points the first character + while(iter != this->begin()) + { + iter = std::prev(iter); + + // range [line_start, iter) represents the previous line + const auto line_start = std::find( + rev_iter(iter), rev_iter(this->begin()), '\n').base(); + const auto comment_found = std::find(line_start, iter, '#'); + if(comment_found == iter) + { + break; // comment not found. + } + + // exclude the following case. + // > a = "foo" # comment // <-- this is not a comment for b but a. + // > b = "current value" + if(std::all_of(line_start, comment_found, + [](const char c) noexcept -> bool { + return c == ' ' || c == '\t'; + })) + { + // unwrap the first '#' by std::next. + auto str = make_string(std::next(comment_found), iter); + if(!str.empty() && str.back() == '\r') {str.pop_back();} + com.push_back(std::move(str)); + } + else + { + break; + } + iter = line_start; + } + } + } + + if(com.size() > 1) + { + std::reverse(com.begin(), com.end()); + } + + { + // find comments just after the current region. + // ```toml + // # not this. + // a = value # this one. + // a = [ # not this (technically difficult) + // + // ] # and this. + // ``` + // The reason why it's difficult is that it requires parsing in the + // following case. + // ```toml + // a = [ 10 # this comment is for `10`. not for `a` but `a[0]`. + // # ... + // ] # this is apparently a comment for a. + // + // b = [ + // 3.14 ] # there is no way to add a comment to `3.14` currently. + // + // c = [ + // 3.14 # do this if you need a comment here. + // ] + // ``` + const auto comment_found = + std::find(this->last(), this->line_end(), '#'); + if(comment_found != this->line_end()) // '#' found + { + // table = {key = "value"} # what is this for? + // the above comment is not for "value", but {key="value"}. + if(comment_found == std::find_if(this->last(), comment_found, + [](const char c) noexcept -> bool { + return !(c == ' ' || c == '\t' || c == ','); + })) + { + // unwrap the first '#' by std::next. + auto str = make_string(std::next(comment_found), this->line_end()); + if(!str.empty() && str.back() == '\r') {str.pop_back();} + com.push_back(std::move(str)); + } + } + } + return com; + } + + private: + + source_ptr source_; + std::string source_name_; + const_iterator first_, last_; +}; + +} // detail +} // toml +#endif// TOML11_REGION_H diff --git a/libraries/toml11/include/toml/result.hpp b/libraries/toml11/include/toml/result.hpp new file mode 100644 index 000000000..77cd46c64 --- /dev/null +++ b/libraries/toml11/include/toml/result.hpp @@ -0,0 +1,717 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_RESULT_HPP +#define TOML11_RESULT_HPP +#include "traits.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace toml +{ + +template +struct success +{ + using value_type = T; + value_type value; + + explicit success(const value_type& v) + noexcept(std::is_nothrow_copy_constructible::value) + : value(v) + {} + explicit success(value_type&& v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template + explicit success(U&& v): value(std::forward(v)) {} + + template + explicit success(const success& v): value(v.value) {} + template + explicit success(success&& v): value(std::move(v.value)) {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; +}; + +template +struct failure +{ + using value_type = T; + value_type value; + + explicit failure(const value_type& v) + noexcept(std::is_nothrow_copy_constructible::value) + : value(v) + {} + explicit failure(value_type&& v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template + explicit failure(U&& v): value(std::forward(v)) {} + + template + explicit failure(const failure& v): value(v.value) {} + template + explicit failure(failure&& v): value(std::move(v.value)) {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; +}; + +template +success::type>::type> +ok(T&& v) +{ + return success< + typename std::remove_cv::type>::type + >(std::forward(v)); +} +template +failure::type>::type> +err(T&& v) +{ + return failure< + typename std::remove_cv::type>::type + >(std::forward(v)); +} + +inline success ok(const char* literal) +{ + return success(std::string(literal)); +} +inline failure err(const char* literal) +{ + return failure(std::string(literal)); +} + + +template +struct result +{ + using value_type = T; + using error_type = E; + using success_type = success; + using failure_type = failure; + + result(const success_type& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(s); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + result(const failure_type& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(f); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + result(success_type&& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + result(failure_type&& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + + template + result(const success& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(s.value); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + template + result(const failure& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(f.value); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + template + result(success&& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + template + result(failure&& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + + result& operator=(const success_type& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(s); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + result& operator=(const failure_type& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(f); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + result& operator=(success_type&& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + result& operator=(failure_type&& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + + template + result& operator=(const success& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(s.value); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + template + result& operator=(const failure& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(f.value); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + template + result& operator=(success&& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + template + result& operator=(failure&& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + + ~result() noexcept {this->cleanup();} + + result(const result& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + result(result&& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + + template + result(const result& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + template + result(result&& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + + result& operator=(const result& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + result& operator=(result&& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + template + result& operator=(const result& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + template + result& operator=(result&& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + bool is_ok() const noexcept {return is_ok_;} + bool is_err() const noexcept {return !is_ok_;} + + operator bool() const noexcept {return is_ok_;} + + value_type& unwrap() & + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return this->succ.value; + } + value_type const& unwrap() const& + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return this->succ.value; + } + value_type&& unwrap() && + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return std::move(this->succ.value); + } + + value_type& unwrap_or(value_type& opt) & + { + if(is_err()) {return opt;} + return this->succ.value; + } + value_type const& unwrap_or(value_type const& opt) const& + { + if(is_err()) {return opt;} + return this->succ.value; + } + value_type unwrap_or(value_type opt) && + { + if(is_err()) {return opt;} + return this->succ.value; + } + + error_type& unwrap_err() & + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return this->fail.value; + } + error_type const& unwrap_err() const& + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return this->fail.value; + } + error_type&& unwrap_err() && + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return std::move(this->fail.value); + } + + value_type& as_ok() & noexcept {return this->succ.value;} + value_type const& as_ok() const& noexcept {return this->succ.value;} + value_type&& as_ok() && noexcept {return std::move(this->succ.value);} + + error_type& as_err() & noexcept {return this->fail.value;} + error_type const& as_err() const& noexcept {return this->fail.value;} + error_type&& as_err() && noexcept {return std::move(this->fail.value);} + + + // prerequisities + // F: T -> U + // retval: result + template + result, error_type> + map(F&& f) & + { + if(this->is_ok()){return ok(f(this->as_ok()));} + return err(this->as_err()); + } + template + result, error_type> + map(F&& f) const& + { + if(this->is_ok()){return ok(f(this->as_ok()));} + return err(this->as_err()); + } + template + result, error_type> + map(F&& f) && + { + if(this->is_ok()){return ok(f(std::move(this->as_ok())));} + return err(std::move(this->as_err())); + } + + // prerequisities + // F: E -> F + // retval: result + template + result> + map_err(F&& f) & + { + if(this->is_err()){return err(f(this->as_err()));} + return ok(this->as_ok()); + } + template + result> + map_err(F&& f) const& + { + if(this->is_err()){return err(f(this->as_err()));} + return ok(this->as_ok()); + } + template + result> + map_err(F&& f) && + { + if(this->is_err()){return err(f(std::move(this->as_err())));} + return ok(std::move(this->as_ok())); + } + + // prerequisities + // F: T -> U + // retval: U + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) & + { + if(this->is_err()){return std::forward(opt);} + return f(this->as_ok()); + } + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) const& + { + if(this->is_err()){return std::forward(opt);} + return f(this->as_ok()); + } + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) && + { + if(this->is_err()){return std::forward(opt);} + return f(std::move(this->as_ok())); + } + + // prerequisities + // F: E -> U + // retval: U + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) & + { + if(this->is_ok()){return std::forward(opt);} + return f(this->as_err()); + } + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) const& + { + if(this->is_ok()){return std::forward(opt);} + return f(this->as_err()); + } + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) && + { + if(this->is_ok()){return std::forward(opt);} + return f(std::move(this->as_err())); + } + + // prerequisities: + // F: func T -> U + // toml::err(error_type) should be convertible to U. + // normally, type U is another result and E is convertible to F + template + detail::return_type_of_t + and_then(F&& f) & + { + if(this->is_ok()){return f(this->as_ok());} + return err(this->as_err()); + } + template + detail::return_type_of_t + and_then(F&& f) const& + { + if(this->is_ok()){return f(this->as_ok());} + return err(this->as_err()); + } + template + detail::return_type_of_t + and_then(F&& f) && + { + if(this->is_ok()){return f(std::move(this->as_ok()));} + return err(std::move(this->as_err())); + } + + // prerequisities: + // F: func E -> U + // toml::ok(value_type) should be convertible to U. + // normally, type U is another result and T is convertible to S + template + detail::return_type_of_t + or_else(F&& f) & + { + if(this->is_err()){return f(this->as_err());} + return ok(this->as_ok()); + } + template + detail::return_type_of_t + or_else(F&& f) const& + { + if(this->is_err()){return f(this->as_err());} + return ok(this->as_ok()); + } + template + detail::return_type_of_t + or_else(F&& f) && + { + if(this->is_err()){return f(std::move(this->as_err()));} + return ok(std::move(this->as_ok())); + } + + // if *this is error, returns *this. otherwise, returns other. + result and_other(const result& other) const& + { + return this->is_err() ? *this : other; + } + result and_other(result&& other) && + { + return this->is_err() ? std::move(*this) : std::move(other); + } + + // if *this is okay, returns *this. otherwise, returns other. + result or_other(const result& other) const& + { + return this->is_ok() ? *this : other; + } + result or_other(result&& other) && + { + return this->is_ok() ? std::move(*this) : std::move(other); + } + + void swap(result& other) + { + result tmp(std::move(*this)); + *this = std::move(other); + other = std::move(tmp); + return ; + } + + private: + + static std::string format_error(std::exception const& excpt) + { + return std::string(excpt.what()); + } + template::value, std::nullptr_t>::type = nullptr> + static std::string format_error(U const& others) + { + std::ostringstream oss; oss << others; + return oss.str(); + } + + void cleanup() noexcept + { + if(this->is_ok_) {this->succ.~success_type();} + else {this->fail.~failure_type();} + return; + } + + private: + + bool is_ok_; + union + { + success_type succ; + failure_type fail; + }; +}; + +template +void swap(result& lhs, result& rhs) +{ + lhs.swap(rhs); + return; +} + +// this might be confusing because it eagerly evaluated, while in the other +// cases operator && and || are short-circuited. +// +// template +// inline result +// operator&&(const result& lhs, const result& rhs) noexcept +// { +// return lhs.is_ok() ? rhs : lhs; +// } +// +// template +// inline result +// operator||(const result& lhs, const result& rhs) noexcept +// { +// return lhs.is_ok() ? lhs : rhs; +// } + +// ---------------------------------------------------------------------------- +// re-use result as a optional with none_t + +namespace detail +{ +struct none_t {}; +inline bool operator==(const none_t&, const none_t&) noexcept {return true;} +inline bool operator!=(const none_t&, const none_t&) noexcept {return false;} +inline bool operator< (const none_t&, const none_t&) noexcept {return false;} +inline bool operator<=(const none_t&, const none_t&) noexcept {return true;} +inline bool operator> (const none_t&, const none_t&) noexcept {return false;} +inline bool operator>=(const none_t&, const none_t&) noexcept {return true;} +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const none_t&) +{ + os << "none"; + return os; +} +inline failure none() noexcept {return failure{none_t{}};} +} // detail +} // toml11 +#endif// TOML11_RESULT_H diff --git a/libraries/toml11/include/toml/serializer.hpp b/libraries/toml11/include/toml/serializer.hpp new file mode 100644 index 000000000..77aef589f --- /dev/null +++ b/libraries/toml11/include/toml/serializer.hpp @@ -0,0 +1,853 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_SERIALIZER_HPP +#define TOML11_SERIALIZER_HPP +#include + +#include + +#include "lexer.hpp" +#include "value.hpp" + +namespace toml +{ + +// This function serialize a key. It checks a string is a bare key and +// escapes special characters if the string is not compatible to a bare key. +// ```cpp +// std::string k("non.bare.key"); // the key itself includes `.`s. +// std::string formatted = toml::format_key(k); +// assert(formatted == "\"non.bare.key\""); +// ``` +// +// This function is exposed to make it easy to write a user-defined serializer. +// Since toml restricts characters available in a bare key, generally a string +// should be escaped. But checking whether a string needs to be surrounded by +// a `"` and escaping some special character is boring. +template +std::basic_string +format_key(const std::basic_string& key) +{ + // check the key can be a bare (unquoted) key + detail::location loc(key, std::vector(key.begin(), key.end())); + detail::lex_unquoted_key::invoke(loc); + if(loc.iter() == loc.end()) + { + return key; // all the tokens are consumed. the key is unquoted-key. + } + + //if it includes special characters, then format it in a "quoted" key. + std::basic_string serialized("\""); + for(const char c : key) + { + switch(c) + { + case '\\': {serialized += "\\\\"; break;} + case '\"': {serialized += "\\\""; break;} + case '\b': {serialized += "\\b"; break;} + case '\t': {serialized += "\\t"; break;} + case '\f': {serialized += "\\f"; break;} + case '\n': {serialized += "\\n"; break;} + case '\r': {serialized += "\\r"; break;} + default : {serialized += c; break;} + } + } + serialized += "\""; + return serialized; +} + +template +std::basic_string +format_keys(const std::vector>& keys) +{ + std::basic_string serialized; + if(keys.empty()) {return serialized;} + + for(const auto& ky : keys) + { + serialized += format_key(ky); + serialized += charT('.'); + } + serialized.pop_back(); // remove the last dot '.' + return serialized; +} + +template +struct serializer +{ + static_assert(detail::is_basic_value::value, + "toml::serializer is for toml::value and its variants, " + "toml::basic_value<...>."); + + using value_type = Value; + using key_type = typename value_type::key_type ; + using comment_type = typename value_type::comment_type ; + using boolean_type = typename value_type::boolean_type ; + using integer_type = typename value_type::integer_type ; + using floating_type = typename value_type::floating_type ; + using string_type = typename value_type::string_type ; + using local_time_type = typename value_type::local_time_type ; + using local_date_type = typename value_type::local_date_type ; + using local_datetime_type = typename value_type::local_datetime_type ; + using offset_datetime_type = typename value_type::offset_datetime_type; + using array_type = typename value_type::array_type ; + using table_type = typename value_type::table_type ; + + serializer(const std::size_t w = 80u, + const int float_prec = std::numeric_limits::max_digits10, + const bool can_be_inlined = false, + const bool no_comment = false, + std::vector ks = {}, + const bool value_has_comment = false) + : can_be_inlined_(can_be_inlined), no_comment_(no_comment), + value_has_comment_(value_has_comment && !no_comment), + float_prec_(float_prec), width_(w), keys_(std::move(ks)) + {} + ~serializer() = default; + + std::string operator()(const boolean_type& b) const + { + return b ? "true" : "false"; + } + std::string operator()(const integer_type i) const + { + return std::to_string(i); + } + std::string operator()(const floating_type f) const + { + const auto fmt = "%.*g"; + const auto bsz = std::snprintf(nullptr, 0, fmt, this->float_prec_, f); + // +1 for null character(\0) + std::vector buf(static_cast(bsz + 1), '\0'); + std::snprintf(buf.data(), buf.size(), fmt, this->float_prec_, f); + + std::string token(buf.begin(), std::prev(buf.end())); + if(!token.empty() && token.back() == '.') // 1. => 1.0 + { + token += '0'; + } + + const auto e = std::find_if( + token.cbegin(), token.cend(), [](const char c) noexcept -> bool { + return c == 'e' || c == 'E'; + }); + const auto has_exponent = (token.cend() != e); + const auto has_fraction = (token.cend() != std::find( + token.cbegin(), token.cend(), '.')); + + if(!has_exponent && !has_fraction) + { + // the resulting value does not have any float specific part! + token += ".0"; + } + return token; + } + std::string operator()(const string_type& s) const + { + if(s.kind == string_t::basic) + { + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\"') != s.str.cend()) + { + // if linefeed or double-quote is contained, + // make it multiline basic string. + const auto escaped = this->escape_ml_basic_string(s.str); + std::string open("\"\"\""); + std::string close("\"\"\""); + if(escaped.find('\n') != std::string::npos || + this->width_ < escaped.size() + 6) + { + // if the string body contains newline or is enough long, + // add newlines after and before delimiters. + open += "\n"; + close = std::string("\\\n") + close; + } + return open + escaped + close; + } + + // no linefeed. try to make it oneline-string. + std::string oneline = this->escape_basic_string(s.str); + if(oneline.size() + 2 < width_ || width_ < 2) + { + const std::string quote("\""); + return quote + oneline + quote; + } + + // the line is too long compared to the specified width. + // split it into multiple lines. + std::string token("\"\"\"\n"); + while(!oneline.empty()) + { + if(oneline.size() < width_) + { + token += oneline; + oneline.clear(); + } + else if(oneline.at(width_-2) == '\\') + { + token += oneline.substr(0, width_-2); + token += "\\\n"; + oneline.erase(0, width_-2); + } + else + { + token += oneline.substr(0, width_-1); + token += "\\\n"; + oneline.erase(0, width_-1); + } + } + return token + std::string("\\\n\"\"\""); + } + else // the string `s` is literal-string. + { + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\'') != s.str.cend() ) + { + std::string open("'''"); + if(this->width_ + 6 < s.str.size()) + { + open += '\n'; // the first newline is ignored by TOML spec + } + const std::string close("'''"); + return open + s.str + close; + } + else + { + const std::string quote("'"); + return quote + s.str + quote; + } + } + } + + std::string operator()(const local_date_type& d) const + { + std::ostringstream oss; + oss << d; + return oss.str(); + } + std::string operator()(const local_time_type& t) const + { + std::ostringstream oss; + oss << t; + return oss.str(); + } + std::string operator()(const local_datetime_type& dt) const + { + std::ostringstream oss; + oss << dt; + return oss.str(); + } + std::string operator()(const offset_datetime_type& odt) const + { + std::ostringstream oss; + oss << odt; + return oss.str(); + } + + std::string operator()(const array_type& v) const + { + if(v.empty()) + { + return std::string("[]"); + } + if(this->is_array_of_tables(v)) + { + return make_array_of_tables(v); + } + + // not an array of tables. normal array. + // first, try to make it inline if none of the elements have a comment. + if( ! this->has_comment_inside(v)) + { + const auto inl = this->make_inline_array(v); + if(inl.size() < this->width_ && + std::find(inl.cbegin(), inl.cend(), '\n') == inl.cend()) + { + return inl; + } + } + + // if the length exceeds this->width_, print multiline array. + // key = [ + // # ... + // 42, + // ... + // ] + std::string token; + std::string current_line; + token += "[\n"; + for(const auto& item : v) + { + if( ! item.comments().empty() && !no_comment_) + { + // if comment exists, the element must be the only element in the line. + // e.g. the following is not allowed. + // ```toml + // array = [ + // # comment for what? + // 1, 2, 3, 4, 5 + // ] + // ``` + if(!current_line.empty()) + { + if(current_line.back() != '\n') + { + current_line += '\n'; + } + token += current_line; + current_line.clear(); + } + for(const auto& c : item.comments()) + { + token += '#'; + token += c; + token += '\n'; + } + token += toml::visit(*this, item); + if(!token.empty() && token.back() == '\n') {token.pop_back();} + token += ",\n"; + continue; + } + std::string next_elem; + next_elem += toml::visit(*this, item); + + // comma before newline. + if(!next_elem.empty() && next_elem.back() == '\n') {next_elem.pop_back();} + + // if current line does not exceeds the width limit, continue. + if(current_line.size() + next_elem.size() + 1 < this->width_) + { + current_line += next_elem; + current_line += ','; + } + else if(current_line.empty()) + { + // if current line was empty, force put the next_elem because + // next_elem is not splittable + token += next_elem; + token += ",\n"; + // current_line is kept empty + } + else // reset current_line + { + assert(current_line.back() == ','); + token += current_line; + token += '\n'; + current_line = next_elem; + current_line += ','; + } + } + if(!current_line.empty()) + { + if(!current_line.empty() && current_line.back() != '\n') + { + current_line += '\n'; + } + token += current_line; + } + token += "]\n"; + return token; + } + + // templatize for any table-like container + std::string operator()(const table_type& v) const + { + // if an element has a comment, then it can't be inlined. + // table = {# how can we write a comment for this? key = "value"} + if(this->can_be_inlined_ && !(this->has_comment_inside(v))) + { + std::string token; + if(!this->keys_.empty()) + { + token += format_key(this->keys_.back()); + token += " = "; + } + token += this->make_inline_table(v); + if(token.size() < this->width_ && + token.end() == std::find(token.begin(), token.end(), '\n')) + { + return token; + } + } + + std::string token; + if(!keys_.empty()) + { + token += '['; + token += format_keys(keys_); + token += "]\n"; + } + token += this->make_multiline_table(v); + return token; + } + + private: + + std::string escape_basic_string(const std::string& s) const + { + //XXX assuming `s` is a valid utf-8 sequence. + std::string retval; + for(const char c : s) + { + switch(c) + { + case '\\': {retval += "\\\\"; break;} + case '\"': {retval += "\\\""; break;} + case '\b': {retval += "\\b"; break;} + case '\t': {retval += "\\t"; break;} + case '\f': {retval += "\\f"; break;} + case '\n': {retval += "\\n"; break;} + case '\r': {retval += "\\r"; break;} + default : {retval += c; break;} + } + } + return retval; + } + + std::string escape_ml_basic_string(const std::string& s) const + { + std::string retval; + for(auto i=s.cbegin(), e=s.cend(); i!=e; ++i) + { + switch(*i) + { + case '\\': {retval += "\\\\"; break;} + // One or two consecutive "s are allowed. + // Later we will check there are no three consecutive "s. + // case '\"': {retval += "\\\""; break;} + case '\b': {retval += "\\b"; break;} + case '\t': {retval += "\\t"; break;} + case '\f': {retval += "\\f"; break;} + case '\n': {retval += "\n"; break;} + case '\r': + { + if(std::next(i) != e && *std::next(i) == '\n') + { + retval += "\r\n"; + ++i; + } + else + { + retval += "\\r"; + } + break; + } + default: {retval += *i; break;} + } + } + // Only 1 or 2 consecutive `"`s are allowed in multiline basic string. + // 3 consecutive `"`s are considered as a closing delimiter. + // We need to check if there are 3 or more consecutive `"`s and insert + // backslash to break them down into several short `"`s like the `str6` + // in the following example. + // ```toml + // str4 = """Here are two quotation marks: "". Simple enough.""" + // # str5 = """Here are three quotation marks: """.""" # INVALID + // str5 = """Here are three quotation marks: ""\".""" + // str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" + // ``` + auto found_3_quotes = retval.find("\"\"\""); + while(found_3_quotes != std::string::npos) + { + retval.replace(found_3_quotes, 3, "\"\"\\\""); + found_3_quotes = retval.find("\"\"\""); + } + return retval; + } + + // if an element of a table or an array has a comment, it cannot be inlined. + bool has_comment_inside(const array_type& a) const noexcept + { + // if no_comment is set, comments would not be written. + if(this->no_comment_) {return false;} + + for(const auto& v : a) + { + if(!v.comments().empty()) {return true;} + } + return false; + } + bool has_comment_inside(const table_type& t) const noexcept + { + // if no_comment is set, comments would not be written. + if(this->no_comment_) {return false;} + + for(const auto& kv : t) + { + if(!kv.second.comments().empty()) {return true;} + } + return false; + } + + std::string make_inline_array(const array_type& v) const + { + assert(!has_comment_inside(v)); + std::string token; + token += '['; + bool is_first = true; + for(const auto& item : v) + { + if(is_first) {is_first = false;} else {token += ',';} + token += visit(serializer( + (std::numeric_limits::max)(), this->float_prec_, + /* inlined */ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !item.comments().empty()), item); + } + token += ']'; + return token; + } + + std::string make_inline_table(const table_type& v) const + { + assert(!has_comment_inside(v)); + assert(this->can_be_inlined_); + std::string token; + token += '{'; + bool is_first = true; + for(const auto& kv : v) + { + // in inline tables, trailing comma is not allowed (toml-lang #569). + if(is_first) {is_first = false;} else {token += ',';} + token += format_key(kv.first); + token += '='; + token += visit(serializer( + (std::numeric_limits::max)(), this->float_prec_, + /* inlined */ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + } + token += '}'; + return token; + } + + std::string make_multiline_table(const table_type& v) const + { + std::string token; + + // print non-table elements first. + // ```toml + // [foo] # a table we're writing now here + // key = "value" # <- non-table element, "key" + // # ... + // [foo.bar] # <- table element, "bar" + // ``` + // because after printing [foo.bar], the remaining non-table values will + // be assigned into [foo.bar], not [foo]. Those values should be printed + // earlier. + for(const auto& kv : v) + { + if(kv.second.is_table() || is_array_of_tables(kv.second)) + { + continue; + } + + token += write_comments(kv.second); + + const auto key_and_sep = format_key(kv.first) + " = "; + const auto residual_width = (this->width_ > key_and_sep.size()) ? + this->width_ - key_and_sep.size() : 0; + token += key_and_sep; + token += visit(serializer(residual_width, this->float_prec_, + /*can be inlined*/ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + + if(token.back() != '\n') + { + token += '\n'; + } + } + + // normal tables / array of tables + + // after multiline table appeared, the other tables cannot be inline + // because the table would be assigned into the table. + // [foo] + // ... + // bar = {...} # <- bar will be a member of [foo]. + bool multiline_table_printed = false; + for(const auto& kv : v) + { + if(!kv.second.is_table() && !is_array_of_tables(kv.second)) + { + continue; // other stuff are already serialized. skip them. + } + + std::vector ks(this->keys_); + ks.push_back(kv.first); + + auto tmp = visit(serializer(this->width_, this->float_prec_, + !multiline_table_printed, this->no_comment_, ks, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + + // If it is the first time to print a multi-line table, it would be + // helpful to separate normal key-value pair and subtables by a + // newline. + // (this checks if the current key-value pair contains newlines. + // but it is not perfect because multi-line string can also contain + // a newline. in such a case, an empty line will be written) TODO + if((!multiline_table_printed) && + std::find(tmp.cbegin(), tmp.cend(), '\n') != tmp.cend()) + { + multiline_table_printed = true; + token += '\n'; // separate key-value pairs and subtables + + token += write_comments(kv.second); + token += tmp; + + // care about recursive tables (all tables in each level prints + // newline and there will be a full of newlines) + if(tmp.substr(tmp.size() - 2, 2) != "\n\n" && + tmp.substr(tmp.size() - 4, 4) != "\r\n\r\n" ) + { + token += '\n'; + } + } + else + { + token += write_comments(kv.second); + token += tmp; + token += '\n'; + } + } + return token; + } + + std::string make_array_of_tables(const array_type& v) const + { + // if it's not inlined, we need to add `[[table.key]]`. + // but if it can be inlined, we can format it as the following. + // ``` + // table.key = [ + // {...}, + // # comment + // {...}, + // ] + // ``` + // This function checks if inlinization is possible or not, and then + // format the array-of-tables in a proper way. + // + // Note about comments: + // + // If the array itself has a comment (value_has_comment_ == true), we + // should try to make it inline. + // ```toml + // # comment about array + // array = [ + // # comment about table element + // {of = "table"} + // ] + // ``` + // If it is formatted as a multiline table, the two comments becomes + // indistinguishable. + // ```toml + // # comment about array + // # comment about table element + // [[array]] + // of = "table" + // ``` + // So we need to try to make it inline, and it force-inlines regardless + // of the line width limit. + // It may fail if the element of a table has comment. In that case, + // the array-of-tables will be formatted as a multiline table. + if(this->can_be_inlined_ || this->value_has_comment_) + { + std::string token; + if(!keys_.empty()) + { + token += format_key(keys_.back()); + token += " = "; + } + + bool failed = false; + token += "[\n"; + for(const auto& item : v) + { + // if an element of the table has a comment, the table + // cannot be inlined. + if(this->has_comment_inside(item.as_table())) + { + failed = true; + break; + } + // write comments for the table itself + token += write_comments(item); + + const auto t = this->make_inline_table(item.as_table()); + + if(t.size() + 1 > width_ || // +1 for the last comma {...}, + std::find(t.cbegin(), t.cend(), '\n') != t.cend()) + { + // if the value itself has a comment, ignore the line width limit + if( ! this->value_has_comment_) + { + failed = true; + break; + } + } + token += t; + token += ",\n"; + } + + if( ! failed) + { + token += "]\n"; + return token; + } + // if failed, serialize them as [[array.of.tables]]. + } + + std::string token; + for(const auto& item : v) + { + token += write_comments(item); + token += "[["; + token += format_keys(keys_); + token += "]]\n"; + token += this->make_multiline_table(item.as_table()); + } + return token; + } + + std::string write_comments(const value_type& v) const + { + std::string retval; + if(this->no_comment_) {return retval;} + + for(const auto& c : v.comments()) + { + retval += '#'; + retval += c; + retval += '\n'; + } + return retval; + } + + bool is_array_of_tables(const value_type& v) const + { + if(!v.is_array() || v.as_array().empty()) {return false;} + return is_array_of_tables(v.as_array()); + } + bool is_array_of_tables(const array_type& v) const + { + // Since TOML v0.5.0, heterogeneous arrays are allowed. So we need to + // check all the element in an array to check if the array is an array + // of tables. + return std::all_of(v.begin(), v.end(), [](const value_type& elem) { + return elem.is_table(); + }); + } + + private: + + bool can_be_inlined_; + bool no_comment_; + bool value_has_comment_; + int float_prec_; + std::size_t width_; + std::vector keys_; +}; + +template class M, template class V> +std::string +format(const basic_value& v, std::size_t w = 80u, + int fprec = std::numeric_limits::max_digits10, + bool no_comment = false, bool force_inline = false) +{ + using value_type = basic_value; + // if value is a table, it is considered to be a root object. + // the root object can't be an inline table. + if(v.is_table()) + { + std::ostringstream oss; + if(!v.comments().empty()) + { + oss << v.comments(); + oss << '\n'; // to split the file comment from the first element + } + const auto serialized = visit(serializer(w, fprec, no_comment, false), v); + oss << serialized; + return oss.str(); + } + return visit(serializer(w, fprec, force_inline), v); +} + +namespace detail +{ +template +int comment_index(std::basic_ostream&) +{ + static const int index = std::ios_base::xalloc(); + return index; +} +} // detail + +template +std::basic_ostream& +nocomment(std::basic_ostream& os) +{ + // by default, it is zero. and by defalut, it shows comments. + os.iword(detail::comment_index(os)) = 1; + return os; +} + +template +std::basic_ostream& +showcomment(std::basic_ostream& os) +{ + // by default, it is zero. and by defalut, it shows comments. + os.iword(detail::comment_index(os)) = 0; + return os; +} + +template class M, template class V> +std::basic_ostream& +operator<<(std::basic_ostream& os, const basic_value& v) +{ + using value_type = basic_value; + + // get status of std::setw(). + const auto w = static_cast(os.width()); + const int fprec = static_cast(os.precision()); + os.width(0); + + // by defualt, iword is initialized byl 0. And by default, toml11 outputs + // comments. So `0` means showcomment. 1 means nocommnet. + const bool no_comment = (1 == os.iword(detail::comment_index(os))); + + if(!no_comment && v.is_table() && !v.comments().empty()) + { + os << v.comments(); + os << '\n'; // to split the file comment from the first element + } + // the root object can't be an inline table. so pass `false`. + const auto serialized = visit(serializer(w, fprec, no_comment, false), v); + os << serialized; + + // if v is a non-table value, and has only one comment, then + // put a comment just after a value. in the following way. + // + // ```toml + // key = "value" # comment. + // ``` + // + // Since the top-level toml object is a table, one who want to put a + // non-table toml value must use this in a following way. + // + // ```cpp + // toml::value v; + // std::cout << "user-defined-key = " << v << std::endl; + // ``` + // + // In this case, it is impossible to put comments before key-value pair. + // The only way to preserve comments is to put all of them after a value. + if(!no_comment && !v.is_table() && !v.comments().empty()) + { + os << " #"; + for(const auto& c : v.comments()) {os << c;} + } + return os; +} + +} // toml +#endif// TOML11_SERIALIZER_HPP diff --git a/libraries/toml11/include/toml/source_location.hpp b/libraries/toml11/include/toml/source_location.hpp new file mode 100644 index 000000000..fa175b5b4 --- /dev/null +++ b/libraries/toml11/include/toml/source_location.hpp @@ -0,0 +1,233 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_SOURCE_LOCATION_HPP +#define TOML11_SOURCE_LOCATION_HPP +#include +#include + +#include "region.hpp" + +namespace toml +{ + +// A struct to contain location in a toml file. +// The interface imitates std::experimental::source_location, +// but not completely the same. +// +// It would be constructed by toml::value. It can be used to generate +// user-defined error messages. +// +// - std::uint_least32_t line() const noexcept +// - returns the line number where the region is on. +// - std::uint_least32_t column() const noexcept +// - returns the column number where the region starts. +// - std::uint_least32_t region() const noexcept +// - returns the size of the region. +// +// +-- line() +-- region of interest (region() == 9) +// v .---+---. +// 12 | value = "foo bar" +// ^ +// +-- column() +// +// - std::string const& file_name() const noexcept; +// - name of the file. +// - std::string const& line_str() const noexcept; +// - the whole line that contains the region of interest. +// +struct source_location +{ + public: + + source_location() + : line_num_(1), column_num_(1), region_size_(1), + file_name_("unknown file"), line_str_("") + {} + + explicit source_location(const detail::region_base* reg) + : line_num_(1), column_num_(1), region_size_(1), + file_name_("unknown file"), line_str_("") + { + if(reg) + { + if(reg->line_num() != detail::region_base().line_num()) + { + line_num_ = static_cast( + std::stoul(reg->line_num())); + } + column_num_ = static_cast(reg->before() + 1); + region_size_ = static_cast(reg->size()); + file_name_ = reg->name(); + line_str_ = reg->line(); + } + } + + explicit source_location(const detail::region& reg) + : line_num_(static_cast(std::stoul(reg.line_num()))), + column_num_(static_cast(reg.before() + 1)), + region_size_(static_cast(reg.size())), + file_name_(reg.name()), + line_str_ (reg.line()) + {} + explicit source_location(const detail::location& loc) + : line_num_(static_cast(std::stoul(loc.line_num()))), + column_num_(static_cast(loc.before() + 1)), + region_size_(static_cast(loc.size())), + file_name_(loc.name()), + line_str_ (loc.line()) + {} + + ~source_location() = default; + source_location(source_location const&) = default; + source_location(source_location &&) = default; + source_location& operator=(source_location const&) = default; + source_location& operator=(source_location &&) = default; + + std::uint_least32_t line() const noexcept {return line_num_;} + std::uint_least32_t column() const noexcept {return column_num_;} + std::uint_least32_t region() const noexcept {return region_size_;} + + std::string const& file_name() const noexcept {return file_name_;} + std::string const& line_str() const noexcept {return line_str_;} + + private: + + std::uint_least32_t line_num_; + std::uint_least32_t column_num_; + std::uint_least32_t region_size_; + std::string file_name_; + std::string line_str_; +}; + +namespace detail +{ + +// internal error message generation. +inline std::string format_underline(const std::string& message, + const std::vector>& loc_com, + const std::vector& helps = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + std::size_t line_num_width = 0; + for(const auto& lc : loc_com) + { + std::uint_least32_t line = lc.first.line(); + std::size_t digit = 0; + while(line != 0) + { + line /= 10; + digit += 1; + } + line_num_width = (std::max)(line_num_width, digit); + } + // 1 is the minimum width + line_num_width = std::max(line_num_width, 1); + + std::ostringstream retval; + + 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'; + } + + const auto format_one_location = [line_num_width] + (std::ostringstream& oss, + const source_location& loc, const std::string& comment) -> void + { + oss << ' ' << color::bold << color::blue + << std::setw(static_cast(line_num_width)) + << std::right << loc.line() << " | " << color::reset + << loc.line_str() << '\n'; + + oss << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " | " << color::reset + << make_string(loc.column()-1 /*1-origin*/, ' '); + + if(loc.region() == 1) + { + // invalid + // ^------ + oss << color::bold << color::red << "^---" << color::reset; + } + else + { + // invalid + // ~~~~~~~ + const auto underline_len = (std::min)( + static_cast(loc.region()), loc.line_str().size()); + oss << color::bold << color::red + << make_string(underline_len, '~') << color::reset; + } + oss << ' '; + oss << comment; + return; + }; + + assert(!loc_com.empty()); + + // --> example.toml + // | + retval << color::bold << color::blue << " --> " << color::reset + << loc_com.front().first.file_name() << '\n'; + retval << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + // 1 | key value + // | ^--- missing = + format_one_location(retval, loc_com.front().first, loc_com.front().second); + + // process the rest of the locations + for(std::size_t i=1; i filename.toml" again + { + retval << color::bold << color::blue << " --> " << color::reset + << curr.first.file_name() << '\n'; + retval << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + } + + format_one_location(retval, curr.first, curr.second); + } + + if(!helps.empty()) + { + retval << '\n'; + retval << make_string(line_num_width + 1, ' '); + retval << color::bold << color::blue << " |" << color::reset; + for(const auto& help : helps) + { + retval << color::bold << "\nHint: " << color::reset; + retval << help; + } + } + return retval.str(); +} + +} // detail +} // toml +#endif// TOML11_SOURCE_LOCATION_HPP diff --git a/libraries/toml11/include/toml/storage.hpp b/libraries/toml11/include/toml/storage.hpp new file mode 100644 index 000000000..202f9035f --- /dev/null +++ b/libraries/toml11/include/toml/storage.hpp @@ -0,0 +1,43 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_STORAGE_HPP +#define TOML11_STORAGE_HPP +#include "utility.hpp" + +namespace toml +{ +namespace detail +{ + +// this contains pointer and deep-copy the content if copied. +// to avoid recursive pointer. +template +struct storage +{ + using value_type = T; + + explicit storage(value_type const& v): ptr(toml::make_unique(v)) {} + explicit storage(value_type&& v): ptr(toml::make_unique(std::move(v))) {} + ~storage() = default; + storage(const storage& rhs): ptr(toml::make_unique(*rhs.ptr)) {} + storage& operator=(const storage& rhs) + { + this->ptr = toml::make_unique(*rhs.ptr); + return *this; + } + storage(storage&&) = default; + storage& operator=(storage&&) = default; + + bool is_ok() const noexcept {return static_cast(ptr);} + + value_type& value() & noexcept {return *ptr;} + value_type const& value() const& noexcept {return *ptr;} + value_type&& value() && noexcept {return std::move(*ptr);} + + private: + std::unique_ptr ptr; +}; + +} // detail +} // toml +#endif// TOML11_STORAGE_HPP diff --git a/libraries/toml11/include/toml/string.hpp b/libraries/toml11/include/toml/string.hpp new file mode 100644 index 000000000..a6ed0801c --- /dev/null +++ b/libraries/toml11/include/toml/string.hpp @@ -0,0 +1,224 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_STRING_HPP +#define TOML11_STRING_HPP +#include + +#include +#include + +#if __cplusplus >= 201703L +#if __has_include() +#include +#endif +#endif + +namespace toml +{ + +enum class string_t : std::uint8_t +{ + basic = 0, + literal = 1, +}; + +struct string +{ + string() = default; + ~string() = default; + string(const string& s) = default; + string(string&& s) = default; + string& operator=(const string& s) = default; + string& operator=(string&& s) = default; + + string(const std::string& s): kind(string_t::basic), str(s){} + string(const std::string& s, string_t k): kind(k), str(s){} + string(const char* s): kind(string_t::basic), str(s){} + string(const char* s, string_t k): kind(k), str(s){} + + string(std::string&& s): kind(string_t::basic), str(std::move(s)){} + string(std::string&& s, string_t k): kind(k), str(std::move(s)){} + + string& operator=(const std::string& s) + {kind = string_t::basic; str = s; return *this;} + string& operator=(std::string&& s) + {kind = string_t::basic; str = std::move(s); return *this;} + + operator std::string& () & noexcept {return str;} + 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){} + + string& operator=(std::string_view s) + {kind = string_t::basic; str = s; return *this;} + + 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; + std::string str; +}; + +inline bool operator==(const string& lhs, const string& rhs) +{ + return lhs.kind == rhs.kind && lhs.str == rhs.str; +} +inline bool operator!=(const string& lhs, const string& rhs) +{ + return !(lhs == rhs); +} +inline bool operator<(const string& lhs, const string& rhs) +{ + return (lhs.kind == rhs.kind) ? (lhs.str < rhs.str) : (lhs.kind < rhs.kind); +} +inline bool operator>(const string& lhs, const string& rhs) +{ + return rhs < lhs; +} +inline bool operator<=(const string& lhs, const string& rhs) +{ + return !(rhs < lhs); +} +inline bool operator>=(const string& lhs, const string& rhs) +{ + return !(lhs < rhs); +} + +inline bool +operator==(const string& lhs, const std::string& rhs) {return lhs.str == rhs;} +inline bool +operator!=(const string& lhs, const std::string& rhs) {return lhs.str != rhs;} +inline bool +operator< (const string& lhs, const std::string& rhs) {return lhs.str < rhs;} +inline bool +operator> (const string& lhs, const std::string& rhs) {return lhs.str > rhs;} +inline bool +operator<=(const string& lhs, const std::string& rhs) {return lhs.str <= rhs;} +inline bool +operator>=(const string& lhs, const std::string& rhs) {return lhs.str >= rhs;} + +inline bool +operator==(const std::string& lhs, const string& rhs) {return lhs == rhs.str;} +inline bool +operator!=(const std::string& lhs, const string& rhs) {return lhs != rhs.str;} +inline bool +operator< (const std::string& lhs, const string& rhs) {return lhs < rhs.str;} +inline bool +operator> (const std::string& lhs, const string& rhs) {return lhs > rhs.str;} +inline bool +operator<=(const std::string& lhs, const string& rhs) {return lhs <= rhs.str;} +inline bool +operator>=(const std::string& lhs, const string& rhs) {return lhs >= rhs.str;} + +inline bool +operator==(const string& lhs, const char* rhs) {return lhs.str == std::string(rhs);} +inline bool +operator!=(const string& lhs, const char* rhs) {return lhs.str != std::string(rhs);} +inline bool +operator< (const string& lhs, const char* rhs) {return lhs.str < std::string(rhs);} +inline bool +operator> (const string& lhs, const char* rhs) {return lhs.str > std::string(rhs);} +inline bool +operator<=(const string& lhs, const char* rhs) {return lhs.str <= std::string(rhs);} +inline bool +operator>=(const string& lhs, const char* rhs) {return lhs.str >= std::string(rhs);} + +inline bool +operator==(const char* lhs, const string& rhs) {return std::string(lhs) == rhs.str;} +inline bool +operator!=(const char* lhs, const string& rhs) {return std::string(lhs) != rhs.str;} +inline bool +operator< (const char* lhs, const string& rhs) {return std::string(lhs) < rhs.str;} +inline bool +operator> (const char* lhs, const string& rhs) {return std::string(lhs) > rhs.str;} +inline bool +operator<=(const char* lhs, const string& rhs) {return std::string(lhs) <= rhs.str;} +inline bool +operator>=(const char* lhs, const string& rhs) {return std::string(lhs) >= rhs.str;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const string& s) +{ + if(s.kind == string_t::basic) + { + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend()) + { + // it contains newline. make it multiline string. + os << "\"\"\"\n"; + for(auto i=s.str.cbegin(), e=s.str.cend(); i!=e; ++i) + { + switch(*i) + { + case '\\': {os << "\\\\"; break;} + case '\"': {os << "\\\""; break;} + case '\b': {os << "\\b"; break;} + case '\t': {os << "\\t"; break;} + case '\f': {os << "\\f"; break;} + case '\n': {os << '\n'; break;} + case '\r': + { + // since it is a multiline string, + // CRLF is not needed to be escaped. + if(std::next(i) != e && *std::next(i) == '\n') + { + os << "\r\n"; + ++i; + } + else + { + os << "\\r"; + } + break; + } + default: {os << *i; break;} + } + } + os << "\\\n\"\"\""; + return os; + } + // no newline. make it inline. + os << "\""; + for(const auto c : s.str) + { + switch(c) + { + case '\\': {os << "\\\\"; break;} + case '\"': {os << "\\\""; break;} + case '\b': {os << "\\b"; break;} + case '\t': {os << "\\t"; break;} + case '\f': {os << "\\f"; break;} + case '\n': {os << "\\n"; break;} + case '\r': {os << "\\r"; break;} + default : {os << c; break;} + } + } + os << "\""; + return os; + } + // the string `s` is literal-string. + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\'') != s.str.cend() ) + { + // contains newline or single quote. make it multiline. + os << "'''\n" << s.str << "'''"; + return os; + } + // normal literal string + os << '\'' << s.str << '\''; + return os; +} + +} // toml +#endif// TOML11_STRING_H diff --git a/libraries/toml11/include/toml/traits.hpp b/libraries/toml11/include/toml/traits.hpp new file mode 100644 index 000000000..eafa6afed --- /dev/null +++ b/libraries/toml11/include/toml/traits.hpp @@ -0,0 +1,300 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_TRAITS_HPP +#define TOML11_TRAITS_HPP +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201703L +#if __has_include() +#include +#endif // has_include() +#endif // cplusplus >= C++17 + +namespace toml +{ +template class T, template class A> +class basic_value; + +namespace detail +{ +// --------------------------------------------------------------------------- +// check whether type T is a kind of container/map class + +struct has_iterator_impl +{ + template static std::true_type check(typename T::iterator*); + template static std::false_type check(...); +}; +struct has_value_type_impl +{ + template static std::true_type check(typename T::value_type*); + template static std::false_type check(...); +}; +struct has_key_type_impl +{ + template static std::true_type check(typename T::key_type*); + template static std::false_type check(...); +}; +struct has_mapped_type_impl +{ + template static std::true_type check(typename T::mapped_type*); + template static std::false_type check(...); +}; +struct has_reserve_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().reserve(std::declval()))*); +}; +struct has_push_back_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().push_back(std::declval()))*); +}; +struct is_comparable_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval() < std::declval())*); +}; + +struct has_from_toml_method_impl +{ + template class Tb, template class A> + static std::true_type check( + decltype(std::declval().from_toml( + std::declval<::toml::basic_value>()))*); + + template class Tb, template class A> + static std::false_type check(...); +}; +struct has_into_toml_method_impl +{ + template + static std::true_type check(decltype(std::declval().into_toml())*); + template + static std::false_type check(...); +}; + +/// Intel C++ compiler can not use decltype in parent class declaration, here +/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 +#ifdef __INTEL_COMPILER +#define decltype(...) std::enable_if::type +#endif + +template +struct has_iterator : decltype(has_iterator_impl::check(nullptr)){}; +template +struct has_value_type : decltype(has_value_type_impl::check(nullptr)){}; +template +struct has_key_type : decltype(has_key_type_impl::check(nullptr)){}; +template +struct has_mapped_type : decltype(has_mapped_type_impl::check(nullptr)){}; +template +struct has_reserve_method : decltype(has_reserve_method_impl::check(nullptr)){}; +template +struct has_push_back_method : decltype(has_push_back_method_impl::check(nullptr)){}; +template +struct is_comparable : decltype(is_comparable_impl::check(nullptr)){}; + +template class Tb, template class A> +struct has_from_toml_method +: decltype(has_from_toml_method_impl::check(nullptr)){}; + +template +struct has_into_toml_method +: decltype(has_into_toml_method_impl::check(nullptr)){}; + +#ifdef __INTEL_COMPILER +#undef decltype +#endif + +// --------------------------------------------------------------------------- +// C++17 and/or/not + +#if __cplusplus >= 201703L + +using std::conjunction; +using std::disjunction; +using std::negation; + +#else + +template struct conjunction : std::true_type{}; +template struct conjunction : T{}; +template +struct conjunction : + std::conditional(T::value), conjunction, T>::type +{}; + +template struct disjunction : std::false_type{}; +template struct disjunction : T {}; +template +struct disjunction : + std::conditional(T::value), T, disjunction>::type +{}; + +template +struct negation : std::integral_constant(T::value)>{}; + +#endif + +// --------------------------------------------------------------------------- +// type checkers + +template struct is_std_pair : std::false_type{}; +template +struct is_std_pair> : std::true_type{}; + +template struct is_std_tuple : std::false_type{}; +template +struct is_std_tuple> : std::true_type{}; + +template struct is_std_forward_list : std::false_type{}; +template +struct is_std_forward_list> : std::true_type{}; + +template struct is_chrono_duration: std::false_type{}; +template +struct is_chrono_duration>: std::true_type{}; + +template +struct is_map : conjunction< // map satisfies all the following conditions + has_iterator, // has T::iterator + has_value_type, // has T::value_type + has_key_type, // has T::key_type + has_mapped_type // has T::mapped_type + >{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; + +template +struct is_container : conjunction< + negation>, // not a map + negation>, // not a std::string +#if __cplusplus >= 201703L + negation>, // not a std::string_view +#endif + has_iterator, // has T::iterator + has_value_type // has T::value_type + >{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; + +template +struct is_basic_value: std::false_type{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template class M, template class V> +struct is_basic_value<::toml::basic_value>: std::true_type{}; + +// --------------------------------------------------------------------------- +// C++14 index_sequence + +#if __cplusplus >= 201402L + +using std::index_sequence; +using std::make_index_sequence; + +#else + +template struct index_sequence{}; + +template struct push_back_index_sequence{}; +template +struct push_back_index_sequence, N> +{ + typedef index_sequence type; +}; + +template +struct index_sequence_maker +{ + typedef typename push_back_index_sequence< + typename index_sequence_maker::type, N>::type type; +}; +template<> +struct index_sequence_maker<0> +{ + typedef index_sequence<0> type; +}; +template +using make_index_sequence = typename index_sequence_maker::type; + +#endif // __cplusplus >= 2014 + +// --------------------------------------------------------------------------- +// C++14 enable_if_t + +#if __cplusplus >= 201402L + +using std::enable_if_t; + +#else + +template +using enable_if_t = typename std::enable_if::type; + +#endif // __cplusplus >= 2014 + +// --------------------------------------------------------------------------- +// return_type_of_t + +#if __cplusplus >= 201703L + +template +using return_type_of_t = std::invoke_result_t; + +#else +// result_of is deprecated after C++17 +template +using return_type_of_t = typename std::result_of::type; + +#endif + +// --------------------------------------------------------------------------- +// is_string_literal +// +// to use this, pass `typename remove_reference::type` to T. + +template +struct is_string_literal: +disjunction< + std::is_same, + conjunction< + std::is_array, + std::is_same::type> + > + >{}; + +// --------------------------------------------------------------------------- +// C++20 remove_cvref_t + +template +struct remove_cvref +{ + using type = typename std::remove_cv< + typename std::remove_reference::type>::type; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +}// detail +}//toml +#endif // TOML_TRAITS diff --git a/libraries/toml11/include/toml/types.hpp b/libraries/toml11/include/toml/types.hpp new file mode 100644 index 000000000..0818ebd1c --- /dev/null +++ b/libraries/toml11/include/toml/types.hpp @@ -0,0 +1,150 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_TYPES_HPP +#define TOML11_TYPES_HPP +#include +#include + +#include "comments.hpp" +#include "datetime.hpp" +#include "string.hpp" +#include "traits.hpp" + +namespace toml +{ + +template class Table, // map-like class + template class Array> // vector-like class +class basic_value; + +using character = char; +using key = std::string; + +using boolean = bool; +using integer = std::int64_t; +using floating = double; // "float" is a keyward, cannot use it here. +// the following stuffs are structs defined here, so aliases are not needed. +// - string +// - offset_datetime +// - offset_datetime +// - local_datetime +// - local_date +// - local_time + +// default toml::value and default array/table. these are defined after defining +// basic_value itself. +// using value = basic_value; +// using array = typename value::array_type; +// using table = typename value::table_type; + +enum class value_t : std::uint8_t +{ + empty = 0, + boolean = 1, + integer = 2, + floating = 3, + string = 4, + offset_datetime = 5, + local_datetime = 6, + local_date = 7, + local_time = 8, + array = 9, + table = 10, +}; + +template +inline std::basic_ostream& +operator<<(std::basic_ostream& os, value_t t) +{ + switch(t) + { + case value_t::boolean : os << "boolean"; return os; + case value_t::integer : os << "integer"; return os; + case value_t::floating : os << "floating"; return os; + case value_t::string : os << "string"; return os; + case value_t::offset_datetime : os << "offset_datetime"; return os; + case value_t::local_datetime : os << "local_datetime"; return os; + case value_t::local_date : os << "local_date"; return os; + case value_t::local_time : os << "local_time"; return os; + case value_t::array : os << "array"; return os; + case value_t::table : os << "table"; return os; + case value_t::empty : os << "empty"; return os; + default : os << "unknown"; return os; + } +} + +template, + typename alloc = std::allocator> +inline std::basic_string stringize(value_t t) +{ + std::basic_ostringstream oss; + oss << t; + return oss.str(); +} + +namespace detail +{ + +// helper to define a type that represents a value_t value. +template +using value_t_constant = std::integral_constant; + +// meta-function that convertes from value_t to the exact toml type that corresponds to. +// It takes toml::basic_value type because array and table types depend on it. +template struct enum_to_type {using type = void ;}; +template struct enum_to_type{using type = void ;}; +template struct enum_to_type{using type = boolean ;}; +template struct enum_to_type{using type = integer ;}; +template struct enum_to_type{using type = floating ;}; +template struct enum_to_type{using type = string ;}; +template struct enum_to_type{using type = offset_datetime ;}; +template struct enum_to_type{using type = local_datetime ;}; +template struct enum_to_type{using type = local_date ;}; +template struct enum_to_type{using type = local_time ;}; +template struct enum_to_type{using type = typename Value::array_type;}; +template struct enum_to_type{using type = typename Value::table_type;}; + +// meta-function that converts from an exact toml type to the enum that corresponds to. +template +struct type_to_enum : std::conditional< + std::is_same::value, // if T == array_type, + value_t_constant, // then value_t::array + typename std::conditional< // else... + std::is_same::value, // if T == table_type + value_t_constant, // then value_t::table + value_t_constant // else value_t::empty + >::type + >::type {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; + +// meta-function that checks the type T is the same as one of the toml::* types. +template +struct is_exact_toml_type : disjunction< + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same + >{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type: is_exact_toml_type{}; + +} // detail +} // toml +#endif// TOML11_TYPES_H diff --git a/libraries/toml11/include/toml/utility.hpp b/libraries/toml11/include/toml/utility.hpp new file mode 100644 index 000000000..113bec610 --- /dev/null +++ b/libraries/toml11/include/toml/utility.hpp @@ -0,0 +1,93 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_UTILITY_HPP +#define TOML11_UTILITY_HPP +#include +#include +#include + +#include "traits.hpp" + +#if __cplusplus >= 201402L +# define TOML11_MARK_AS_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(__GNUC__) +# define TOML11_MARK_AS_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +# define TOML11_MARK_AS_DEPRECATED(msg) __declspec(deprecated(msg)) +#else +# define TOML11_MARK_AS_DEPRECATED +#endif + +namespace toml +{ + +#if __cplusplus >= 201402L + +using std::make_unique; + +#else + +template +inline std::unique_ptr make_unique(Ts&& ... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +#endif // __cplusplus >= 2014 + +namespace detail +{ +template +void try_reserve_impl(Container& container, std::size_t N, std::true_type) +{ + container.reserve(N); + return; +} +template +void try_reserve_impl(Container&, std::size_t, std::false_type) noexcept +{ + return; +} +} // detail + +template +void try_reserve(Container& container, std::size_t N) +{ + if(N <= container.size()) {return;} + detail::try_reserve_impl(container, N, detail::has_reserve_method{}); + return; +} + +namespace detail +{ +inline std::string concat_to_string_impl(std::ostringstream& oss) +{ + return oss.str(); +} +template +std::string concat_to_string_impl(std::ostringstream& oss, T&& head, Ts&& ... tail) +{ + oss << std::forward(head); + return concat_to_string_impl(oss, std::forward(tail) ... ); +} +} // detail + +template +std::string concat_to_string(Ts&& ... args) +{ + std::ostringstream oss; + oss << std::boolalpha << std::fixed; + return detail::concat_to_string_impl(oss, std::forward(args) ...); +} + +template +T from_string(const std::string& str, T opt) +{ + T v(opt); + std::istringstream iss(str); + iss >> v; + return v; +} + +}// toml +#endif // TOML11_UTILITY diff --git a/libraries/toml11/include/toml/value.hpp b/libraries/toml11/include/toml/value.hpp new file mode 100644 index 000000000..e374c3b5f --- /dev/null +++ b/libraries/toml11/include/toml/value.hpp @@ -0,0 +1,2023 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_VALUE_HPP +#define TOML11_VALUE_HPP +#include + +#include "comments.hpp" +#include "exception.hpp" +#include "into.hpp" +#include "region.hpp" +#include "source_location.hpp" +#include "storage.hpp" +#include "traits.hpp" +#include "types.hpp" +#include "utility.hpp" + +namespace toml +{ + +namespace detail +{ + +// to show error messages. not recommended for users. +template +inline region_base const* get_region(const Value& v) +{ + return v.region_info_.get(); +} + +template +void change_region(Value& v, region reg) +{ + v.region_info_ = std::make_shared(std::move(reg)); + return; +} + +template +[[noreturn]] inline void +throw_bad_cast(const std::string& funcname, value_t actual, const Value& v) +{ + throw type_error(detail::format_underline( + concat_to_string(funcname, "bad_cast to ", Expected), { + {v.location(), concat_to_string("the actual type is ", actual)} + }), v.location()); +} + +// Throw `out_of_range` from `toml::value::at()` and `toml::find()` +// after generating an error message. +// +// The implementation is a bit complicated and there are many edge-cases. +// If you are not interested in the error message generation, just skip this. +template +[[noreturn]] void +throw_key_not_found_error(const Value& v, const key& ky) +{ + // The top-level table has its region at the first character of the file. + // That means that, in the case when a key is not found in the top-level + // table, the error message points to the first character. If the file has + // its first table at the first line, the error message would be like this. + // ```console + // [error] key "a" not found + // --> example.toml + // | + // 1 | [table] + // | ^------ in this table + // ``` + // It actually points to the top-level table at the first character, + // not `[table]`. But it is too confusing. To avoid the confusion, the error + // message should explicitly say "key not found in the top-level table". + const auto loc = v.location(); + if(loc.line() == 1 && loc.region() == 1) + { + // Here it assumes that top-level table starts at the first character. + // The region corresponds to the top-level table will be generated at + // `parse_toml_file` function. + // It also assumes that the top-level table size is just one and + // the line number is `1`. It is always satisfied. And those conditions + // are satisfied only if the table is the top-level table. + // + // 1. one-character dot-key at the first line + // ```toml + // a.b = "c" + // ``` + // toml11 counts whole key as the table key. Here, `a.b` is the region + // of the table "a". It could be counter intuitive, but it works. + // The size of the region is 3, not 1. The above example is the shortest + // dot-key example. The size cannot be 1. + // + // 2. one-character inline-table at the first line + // ```toml + // a = {b = "c"} + // ``` + // toml11 consideres the inline table body as the table region. Here, + // `{b = "c"}` is the region of the table "a". The size of the region + // is 9, not 1. The shotest inline table still has two characters, `{` + // and `}`. The size cannot be 1. + // + // 3. one-character table declaration at the first line + // ```toml + // [a] + // ``` + // toml11 consideres the whole table key as the table region. Here, + // `[a]` is the table region. The size is 3, not 1. + // + throw std::out_of_range(format_underline(concat_to_string( + "key \"", ky, "\" not found in the top-level table"), { + {loc, "the top-level table starts here"} + })); + } + else + { + // normal table. + throw std::out_of_range(format_underline(concat_to_string( + "key \"", ky, "\" not found"), { {loc, "in this table"} })); + } +} + +// switch by `value_t` at the compile time. +template +struct switch_cast {}; +#define TOML11_GENERATE_SWITCH_CASTER(TYPE) \ + template<> \ + struct switch_cast \ + { \ + template \ + static typename Value::TYPE##_type& invoke(Value& v) \ + { \ + return v.as_##TYPE(); \ + } \ + template \ + static typename Value::TYPE##_type const& invoke(const Value& v) \ + { \ + return v.as_##TYPE(); \ + } \ + template \ + static typename Value::TYPE##_type&& invoke(Value&& v) \ + { \ + return std::move(v).as_##TYPE(); \ + } \ + }; \ + /**/ +TOML11_GENERATE_SWITCH_CASTER(boolean) +TOML11_GENERATE_SWITCH_CASTER(integer) +TOML11_GENERATE_SWITCH_CASTER(floating) +TOML11_GENERATE_SWITCH_CASTER(string) +TOML11_GENERATE_SWITCH_CASTER(offset_datetime) +TOML11_GENERATE_SWITCH_CASTER(local_datetime) +TOML11_GENERATE_SWITCH_CASTER(local_date) +TOML11_GENERATE_SWITCH_CASTER(local_time) +TOML11_GENERATE_SWITCH_CASTER(array) +TOML11_GENERATE_SWITCH_CASTER(table) + +#undef TOML11_GENERATE_SWITCH_CASTER + +}// detail + +template class Table = std::unordered_map, + template class Array = std::vector> +class basic_value +{ + template + static void assigner(T& dst, U&& v) + { + const auto tmp = ::new(std::addressof(dst)) T(std::forward(v)); + assert(tmp == std::addressof(dst)); + (void)tmp; + } + + using region_base = detail::region_base; + + template class T, + template class A> + friend class basic_value; + + public: + + using comment_type = Comment; + using key_type = ::toml::key; + using value_type = basic_value; + using boolean_type = ::toml::boolean; + using integer_type = ::toml::integer; + using floating_type = ::toml::floating; + using string_type = ::toml::string; + using local_time_type = ::toml::local_time; + using local_date_type = ::toml::local_date; + using local_datetime_type = ::toml::local_datetime; + using offset_datetime_type = ::toml::offset_datetime; + using array_type = Array; + using table_type = Table; + + public: + + basic_value() noexcept + : type_(value_t::empty), + region_info_(std::make_shared(region_base{})) + {} + ~basic_value() noexcept {this->cleanup();} + + basic_value(const basic_value& v) + : type_(v.type()), region_info_(v.region_info_), comments_(v.comments_) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + } + basic_value(basic_value&& v) + : type_(v.type()), region_info_(std::move(v.region_info_)), + comments_(std::move(v.comments_)) + { + switch(this->type_) // here this->type_ is already initialized + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + } + basic_value& operator=(const basic_value& v) + { + this->cleanup(); + this->region_info_ = v.region_info_; + this->comments_ = v.comments_; + this->type_ = v.type(); + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + return *this; + } + basic_value& operator=(basic_value&& v) + { + this->cleanup(); + this->region_info_ = std::move(v.region_info_); + this->comments_ = std::move(v.comments_); + this->type_ = v.type(); + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + return *this; + } + + // overwrite comments ---------------------------------------------------- + + basic_value(const basic_value& v, std::vector comments) + : type_(v.type()), region_info_(v.region_info_), + comments_(std::move(comments)) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + } + + basic_value(basic_value&& v, std::vector comments) + : type_(v.type()), region_info_(std::move(v.region_info_)), + comments_(std::move(comments)) + { + switch(this->type_) // here this->type_ is already initialized + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + } + + // ----------------------------------------------------------------------- + // conversion between different basic_values. + template class T, + template class A> + basic_value(const basic_value& v) + : type_(v.type()), region_info_(v.region_info_), comments_(v.comments()) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + } + template class T, + template class A> + basic_value(const basic_value& v, std::vector comments) + : type_(v.type()), region_info_(v.region_info_), + comments_(std::move(comments)) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + } + template class T, + template class A> + basic_value& operator=(const basic_value& v) + { + this->region_info_ = v.region_info_; + this->comments_ = comment_type(v.comments()); + this->type_ = v.type(); + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + return *this; + } + + // boolean ============================================================== + + basic_value(boolean b) + : type_(value_t::boolean), + region_info_(std::make_shared(region_base{})) + { + assigner(this->boolean_, b); + } + basic_value& operator=(boolean b) + { + this->cleanup(); + this->type_ = value_t::boolean; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->boolean_, b); + return *this; + } + basic_value(boolean b, std::vector comments) + : type_(value_t::boolean), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->boolean_, b); + } + + // integer ============================================================== + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value(T i) + : type_(value_t::integer), + region_info_(std::make_shared(region_base{})) + { + assigner(this->integer_, static_cast(i)); + } + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value& operator=(T i) + { + this->cleanup(); + this->type_ = value_t::integer; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->integer_, static_cast(i)); + return *this; + } + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value(T i, std::vector comments) + : type_(value_t::integer), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->integer_, static_cast(i)); + } + + // floating ============================================================= + + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f) + : type_(value_t::floating), + region_info_(std::make_shared(region_base{})) + { + assigner(this->floating_, static_cast(f)); + } + + + template::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(T f) + { + this->cleanup(); + this->type_ = value_t::floating; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->floating_, static_cast(f)); + return *this; + } + + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f, std::vector comments) + : type_(value_t::floating), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->floating_, f); + } + + // string =============================================================== + + basic_value(toml::string s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, std::move(s)); + } + basic_value& operator=(toml::string s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, s); + return *this; + } + basic_value(toml::string s, std::vector comments) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->string_, std::move(s)); + } + + basic_value(std::string s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::move(s))); + } + basic_value& operator=(std::string s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(std::move(s))); + return *this; + } + basic_value(std::string s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::move(s), kind)); + } + basic_value(std::string s, std::vector comments) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->string_, toml::string(std::move(s))); + } + basic_value(std::string s, string_t kind, std::vector comments) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->string_, toml::string(std::move(s), kind)); + } + + basic_value(const char* s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::string(s))); + } + basic_value& operator=(const char* s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(std::string(s))); + return *this; + } + basic_value(const char* s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::string(s), kind)); + } + basic_value(const char* s, std::vector comments) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->string_, toml::string(std::string(s))); + } + basic_value(const char* s, string_t kind, std::vector comments) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->string_, toml::string(std::string(s), kind)); + } + +#if __cplusplus >= 201703L + basic_value(std::string_view s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(s)); + } + basic_value& operator=(std::string_view s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(s)); + return *this; + } + basic_value(std::string_view s, std::vector comments) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->string_, toml::string(s)); + } + basic_value(std::string_view s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(s, kind)); + } + basic_value(std::string_view s, string_t kind, std::vector comments) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->string_, toml::string(s, kind)); + } +#endif + + // local date =========================================================== + + basic_value(const local_date& ld) + : type_(value_t::local_date), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_date_, ld); + } + basic_value& operator=(const local_date& ld) + { + this->cleanup(); + this->type_ = value_t::local_date; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_date_, ld); + return *this; + } + basic_value(const local_date& ld, std::vector comments) + : type_(value_t::local_date), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->local_date_, ld); + } + + // local time =========================================================== + + basic_value(const local_time& lt) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_time_, lt); + } + basic_value(const local_time& lt, std::vector comments) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->local_time_, lt); + } + basic_value& operator=(const local_time& lt) + { + this->cleanup(); + this->type_ = value_t::local_time; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_time_, lt); + return *this; + } + + template + basic_value(const std::chrono::duration& dur) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_time_, local_time(dur)); + } + template + basic_value(const std::chrono::duration& dur, + std::vector comments) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->local_time_, local_time(dur)); + } + template + basic_value& operator=(const std::chrono::duration& dur) + { + this->cleanup(); + this->type_ = value_t::local_time; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_time_, local_time(dur)); + return *this; + } + + // local datetime ======================================================= + + basic_value(const local_datetime& ldt) + : type_(value_t::local_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_datetime_, ldt); + } + basic_value(const local_datetime& ldt, std::vector comments) + : type_(value_t::local_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->local_datetime_, ldt); + } + basic_value& operator=(const local_datetime& ldt) + { + this->cleanup(); + this->type_ = value_t::local_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_datetime_, ldt); + return *this; + } + + // offset datetime ====================================================== + + basic_value(const offset_datetime& odt) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->offset_datetime_, odt); + } + basic_value(const offset_datetime& odt, std::vector comments) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->offset_datetime_, odt); + } + basic_value& operator=(const offset_datetime& odt) + { + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->offset_datetime_, odt); + return *this; + } + basic_value(const std::chrono::system_clock::time_point& tp) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->offset_datetime_, offset_datetime(tp)); + } + basic_value(const std::chrono::system_clock::time_point& tp, + std::vector comments) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->offset_datetime_, offset_datetime(tp)); + } + basic_value& operator=(const std::chrono::system_clock::time_point& tp) + { + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->offset_datetime_, offset_datetime(tp)); + return *this; + } + + // array ================================================================ + + basic_value(const array_type& ary) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + assigner(this->array_, ary); + } + basic_value(const array_type& ary, std::vector comments) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->array_, ary); + } + basic_value& operator=(const array_type& ary) + { + this->cleanup(); + this->type_ = value_t::array ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->array_, ary); + return *this; + } + + // array (initializer_list) ---------------------------------------------- + + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::initializer_list list) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + } + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::initializer_list list, std::vector comments) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + } + template::value, + std::nullptr_t>::type = nullptr> + basic_value& operator=(std::initializer_list list) + { + this->cleanup(); + this->type_ = value_t::array; + this->region_info_ = std::make_shared(region_base{}); + + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + return *this; + } + + // array (STL Containers) ------------------------------------------------ + + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value(const T& list) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + } + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value(const T& list, std::vector comments) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + } + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const T& list) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + this->cleanup(); + this->type_ = value_t::array; + this->region_info_ = std::make_shared(region_base{}); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + return *this; + } + + // table ================================================================ + + basic_value(const table_type& tab) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + assigner(this->table_, tab); + } + basic_value(const table_type& tab, std::vector comments) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + assigner(this->table_, tab); + } + basic_value& operator=(const table_type& tab) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->table_, tab); + return *this; + } + + // initializer-list ------------------------------------------------------ + + basic_value(std::initializer_list> list) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + + basic_value(std::initializer_list> list, + std::vector comments) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + basic_value& operator=(std::initializer_list> list) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + return *this; + } + + // other table-like ----------------------------------------------------- + + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value(const Map& mp) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value(const Map& mp, std::vector comments) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(comments)) + { + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const Map& mp) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + return *this; + } + + // user-defined ========================================================= + + // convert using into_toml() method ------------------------------------- + + template::value, std::nullptr_t>::type = nullptr> + basic_value(const T& ud): basic_value(ud.into_toml()) {} + + template::value, std::nullptr_t>::type = nullptr> + basic_value(const T& ud, std::vector comments) + : basic_value(ud.into_toml(), std::move(comments)) + {} + template::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const T& ud) + { + *this = ud.into_toml(); + return *this; + } + + // convert using into struct ----------------------------------------- + + template)> + basic_value(const T& ud): basic_value(::toml::into::into_toml(ud)) {} + template)> + basic_value(const T& ud, std::vector comments) + : basic_value(::toml::into::into_toml(ud), std::move(comments)) + {} + template)> + basic_value& operator=(const T& ud) + { + *this = ::toml::into::into_toml(ud); + return *this; + } + + // for internal use ------------------------------------------------------ + // + // Those constructors take detail::region that contains parse result. + + basic_value(boolean b, detail::region reg, std::vector cm) + : type_(value_t::boolean), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->boolean_, b); + } + template, detail::negation> + >::value, std::nullptr_t>::type = nullptr> + basic_value(T i, detail::region reg, std::vector cm) + : type_(value_t::integer), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->integer_, static_cast(i)); + } + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f, detail::region reg, std::vector cm) + : type_(value_t::floating), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->floating_, static_cast(f)); + } + basic_value(toml::string s, detail::region reg, + std::vector cm) + : type_(value_t::string), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->string_, std::move(s)); + } + basic_value(const local_date& ld, detail::region reg, + std::vector cm) + : type_(value_t::local_date), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_date_, ld); + } + basic_value(const local_time& lt, detail::region reg, + std::vector cm) + : type_(value_t::local_time), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_time_, lt); + } + basic_value(const local_datetime& ldt, detail::region reg, + std::vector cm) + : type_(value_t::local_datetime), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_datetime_, ldt); + } + basic_value(const offset_datetime& odt, detail::region reg, + std::vector cm) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->offset_datetime_, odt); + } + basic_value(const array_type& ary, detail::region reg, + std::vector cm) + : type_(value_t::array), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->array_, ary); + } + basic_value(const table_type& tab, detail::region reg, + std::vector cm) + : type_(value_t::table), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->table_, tab); + } + + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::pair parse_result, std::vector comments) + : basic_value(std::move(parse_result.first), + std::move(parse_result.second), + std::move(comments)) + {} + + // type checking and casting ============================================ + + template::value, + std::nullptr_t>::type = nullptr> + bool is() const noexcept + { + return detail::type_to_enum::value == this->type_; + } + bool is(value_t t) const noexcept {return t == this->type_;} + + bool is_uninitialized() const noexcept {return this->is(value_t::empty );} + bool is_boolean() const noexcept {return this->is(value_t::boolean );} + bool is_integer() const noexcept {return this->is(value_t::integer );} + bool is_floating() const noexcept {return this->is(value_t::floating );} + bool is_string() const noexcept {return this->is(value_t::string );} + bool is_offset_datetime() const noexcept {return this->is(value_t::offset_datetime);} + bool is_local_datetime() const noexcept {return this->is(value_t::local_datetime );} + bool is_local_date() const noexcept {return this->is(value_t::local_date );} + bool is_local_time() const noexcept {return this->is(value_t::local_time );} + bool is_array() const noexcept {return this->is(value_t::array );} + bool is_table() const noexcept {return this->is(value_t::table );} + + value_t type() const noexcept {return type_;} + + template + typename detail::enum_to_type::type& cast() & + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(*this); + } + template + typename detail::enum_to_type::type const& cast() const& + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(*this); + } + template + typename detail::enum_to_type::type&& cast() && + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(std::move(*this)); + } + + // ------------------------------------------------------------------------ + // nothrow version + + boolean const& as_boolean (const std::nothrow_t&) const& noexcept {return this->boolean_;} + integer const& as_integer (const std::nothrow_t&) const& noexcept {return this->integer_;} + floating const& as_floating (const std::nothrow_t&) const& noexcept {return this->floating_;} + string const& as_string (const std::nothrow_t&) const& noexcept {return this->string_;} + offset_datetime const& as_offset_datetime(const std::nothrow_t&) const& noexcept {return this->offset_datetime_;} + local_datetime const& as_local_datetime (const std::nothrow_t&) const& noexcept {return this->local_datetime_;} + local_date const& as_local_date (const std::nothrow_t&) const& noexcept {return this->local_date_;} + local_time const& as_local_time (const std::nothrow_t&) const& noexcept {return this->local_time_;} + array_type const& as_array (const std::nothrow_t&) const& noexcept {return this->array_.value();} + table_type const& as_table (const std::nothrow_t&) const& noexcept {return this->table_.value();} + + boolean & as_boolean (const std::nothrow_t&) & noexcept {return this->boolean_;} + integer & as_integer (const std::nothrow_t&) & noexcept {return this->integer_;} + floating & as_floating (const std::nothrow_t&) & noexcept {return this->floating_;} + string & as_string (const std::nothrow_t&) & noexcept {return this->string_;} + offset_datetime& as_offset_datetime(const std::nothrow_t&) & noexcept {return this->offset_datetime_;} + local_datetime & as_local_datetime (const std::nothrow_t&) & noexcept {return this->local_datetime_;} + local_date & as_local_date (const std::nothrow_t&) & noexcept {return this->local_date_;} + local_time & as_local_time (const std::nothrow_t&) & noexcept {return this->local_time_;} + array_type & as_array (const std::nothrow_t&) & noexcept {return this->array_.value();} + table_type & as_table (const std::nothrow_t&) & noexcept {return this->table_.value();} + + boolean && as_boolean (const std::nothrow_t&) && noexcept {return std::move(this->boolean_);} + integer && as_integer (const std::nothrow_t&) && noexcept {return std::move(this->integer_);} + floating && as_floating (const std::nothrow_t&) && noexcept {return std::move(this->floating_);} + string && as_string (const std::nothrow_t&) && noexcept {return std::move(this->string_);} + offset_datetime&& as_offset_datetime(const std::nothrow_t&) && noexcept {return std::move(this->offset_datetime_);} + local_datetime && as_local_datetime (const std::nothrow_t&) && noexcept {return std::move(this->local_datetime_);} + local_date && as_local_date (const std::nothrow_t&) && noexcept {return std::move(this->local_date_);} + local_time && as_local_time (const std::nothrow_t&) && noexcept {return std::move(this->local_time_);} + array_type && as_array (const std::nothrow_t&) && noexcept {return std::move(this->array_.value());} + table_type && as_table (const std::nothrow_t&) && noexcept {return std::move(this->table_.value());} + + // ======================================================================== + // throw version + // ------------------------------------------------------------------------ + // const reference {{{ + + boolean const& as_boolean() const& + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return this->boolean_; + } + integer const& as_integer() const& + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return this->integer_; + } + floating const& as_floating() const& + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return this->floating_; + } + string const& as_string() const& + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return this->string_; + } + offset_datetime const& as_offset_datetime() const& + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return this->offset_datetime_; + } + local_datetime const& as_local_datetime() const& + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return this->local_datetime_; + } + local_date const& as_local_date() const& + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return this->local_date_; + } + local_time const& as_local_time() const& + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return this->local_time_; + } + array_type const& as_array() const& + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return this->array_.value(); + } + table_type const& as_table() const& + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return this->table_.value(); + } + // }}} + // ------------------------------------------------------------------------ + // nonconst reference {{{ + + boolean & as_boolean() & + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return this->boolean_; + } + integer & as_integer() & + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return this->integer_; + } + floating & as_floating() & + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return this->floating_; + } + string & as_string() & + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return this->string_; + } + offset_datetime & as_offset_datetime() & + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return this->offset_datetime_; + } + local_datetime & as_local_datetime() & + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return this->local_datetime_; + } + local_date & as_local_date() & + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return this->local_date_; + } + local_time & as_local_time() & + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return this->local_time_; + } + array_type & as_array() & + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return this->array_.value(); + } + table_type & as_table() & + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return this->table_.value(); + } + + // }}} + // ------------------------------------------------------------------------ + // rvalue reference {{{ + + boolean && as_boolean() && + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return std::move(this->boolean_); + } + integer && as_integer() && + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return std::move(this->integer_); + } + floating && as_floating() && + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return std::move(this->floating_); + } + string && as_string() && + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return std::move(this->string_); + } + offset_datetime && as_offset_datetime() && + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return std::move(this->offset_datetime_); + } + local_datetime && as_local_datetime() && + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return std::move(this->local_datetime_); + } + local_date && as_local_date() && + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return std::move(this->local_date_); + } + local_time && as_local_time() && + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return std::move(this->local_time_); + } + array_type && as_array() && + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return std::move(this->array_.value()); + } + table_type && as_table() && + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return std::move(this->table_.value()); + } + // }}} + + // accessors ============================================================= + // + // may throw type_error or out_of_range + // + value_type& at(const key& k) + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::at(key): ", this->type_, *this); + } + if(this->as_table(std::nothrow).count(k) == 0) + { + detail::throw_key_not_found_error(*this, k); + } + return this->as_table(std::nothrow).at(k); + } + value_type const& at(const key& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::at(key): ", this->type_, *this); + } + if(this->as_table(std::nothrow).count(k) == 0) + { + detail::throw_key_not_found_error(*this, k); + } + return this->as_table(std::nothrow).at(k); + } + value_type& operator[](const key& k) + { + if(this->is_uninitialized()) + { + *this = table_type{}; + } + else if(!this->is_table()) // initialized, but not a table + { + detail::throw_bad_cast( + "toml::value::operator[](key): ", this->type_, *this); + } + return this->as_table(std::nothrow)[k]; + } + + value_type& at(const std::size_t idx) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::at(idx): ", this->type_, *this); + } + if(this->as_array(std::nothrow).size() <= idx) + { + throw std::out_of_range(detail::format_underline( + "toml::value::at(idx): no element corresponding to the index", { + {this->location(), concat_to_string("the length is ", + this->as_array(std::nothrow).size(), + ", and the specified index is ", idx)} + })); + } + return this->as_array().at(idx); + } + value_type const& at(const std::size_t idx) const + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::at(idx): ", this->type_, *this); + } + if(this->as_array(std::nothrow).size() <= idx) + { + throw std::out_of_range(detail::format_underline( + "toml::value::at(idx): no element corresponding to the index", { + {this->location(), concat_to_string("the length is ", + this->as_array(std::nothrow).size(), + ", and the specified index is ", idx)} + })); + } + return this->as_array(std::nothrow).at(idx); + } + + value_type& operator[](const std::size_t idx) noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + value_type const& operator[](const std::size_t idx) const noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + + void push_back(const value_type& x) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::push_back(value): ", this->type_, *this); + } + this->as_array(std::nothrow).push_back(x); + return; + } + void push_back(value_type&& x) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::push_back(value): ", this->type_, *this); + } + this->as_array(std::nothrow).push_back(std::move(x)); + return; + } + + template + value_type& emplace_back(Ts&& ... args) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::emplace_back(...): ", this->type_, *this); + } + this->as_array(std::nothrow).emplace_back(std::forward(args) ...); + return this->as_array(std::nothrow).back(); + } + + std::size_t size() const + { + switch(this->type_) + { + case value_t::array: + { + return this->as_array(std::nothrow).size(); + } + case value_t::table: + { + return this->as_table(std::nothrow).size(); + } + case value_t::string: + { + return this->as_string(std::nothrow).str.size(); + } + default: + { + throw type_error(detail::format_underline( + "toml::value::size(): bad_cast to container types", { + {this->location(), + concat_to_string("the actual type is ", this->type_)} + }), this->location()); + } + } + } + + std::size_t count(const key_type& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::count(key): ", this->type_, *this); + } + return this->as_table(std::nothrow).count(k); + } + + bool contains(const key_type& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::contains(key): ", this->type_, *this); + } + return (this->as_table(std::nothrow).count(k) != 0); + } + + source_location location() const + { + return source_location(this->region_info_.get()); + } + + comment_type const& comments() const noexcept {return this->comments_;} + comment_type& comments() noexcept {return this->comments_;} + + private: + + void cleanup() noexcept + { + switch(this->type_) + { + case value_t::string : {string_.~string(); return;} + case value_t::array : {array_.~array_storage(); return;} + case value_t::table : {table_.~table_storage(); return;} + default : return; + } + } + + // for error messages + template + friend region_base const* detail::get_region(const Value& v); + + template + friend void detail::change_region(Value& v, detail::region reg); + + private: + + using array_storage = detail::storage; + using table_storage = detail::storage; + + value_t type_; + union + { + boolean boolean_; + integer integer_; + floating floating_; + string string_; + offset_datetime offset_datetime_; + local_datetime local_datetime_; + local_date local_date_; + local_time local_time_; + array_storage array_; + table_storage table_; + }; + std::shared_ptr region_info_; + comment_type comments_; +}; + +// default toml::value and default array/table. +using value = basic_value; +using array = typename value::array_type; +using table = typename value::table_type; + +template class T, template class A> +inline bool +operator==(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()) {return false;} + if(lhs.comments() != rhs.comments()) {return false;} + + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() == rhs.as_boolean(); + } + case value_t::integer : + { + return lhs.as_integer() == rhs.as_integer(); + } + case value_t::floating : + { + return lhs.as_floating() == rhs.as_floating(); + } + case value_t::string : + { + return lhs.as_string() == rhs.as_string(); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() == rhs.as_offset_datetime(); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() == rhs.as_local_datetime(); + } + case value_t::local_date: + { + return lhs.as_local_date() == rhs.as_local_date(); + } + case value_t::local_time: + { + return lhs.as_local_time() == rhs.as_local_time(); + } + case value_t::array : + { + return lhs.as_array() == rhs.as_array(); + } + case value_t::table : + { + return lhs.as_table() == rhs.as_table(); + } + case value_t::empty : {return true; } + default: {return false;} + } +} + +template class T, template class A> +inline bool operator!=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs == rhs); +} + +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator<(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()){return (lhs.type() < rhs.type());} + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() < rhs.as_boolean() || + (lhs.as_boolean() == rhs.as_boolean() && + lhs.comments() < rhs.comments()); + } + case value_t::integer : + { + return lhs.as_integer() < rhs.as_integer() || + (lhs.as_integer() == rhs.as_integer() && + lhs.comments() < rhs.comments()); + } + case value_t::floating : + { + return lhs.as_floating() < rhs.as_floating() || + (lhs.as_floating() == rhs.as_floating() && + lhs.comments() < rhs.comments()); + } + case value_t::string : + { + return lhs.as_string() < rhs.as_string() || + (lhs.as_string() == rhs.as_string() && + lhs.comments() < rhs.comments()); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() < rhs.as_offset_datetime() || + (lhs.as_offset_datetime() == rhs.as_offset_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() < rhs.as_local_datetime() || + (lhs.as_local_datetime() == rhs.as_local_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_date: + { + return lhs.as_local_date() < rhs.as_local_date() || + (lhs.as_local_date() == rhs.as_local_date() && + lhs.comments() < rhs.comments()); + } + case value_t::local_time: + { + return lhs.as_local_time() < rhs.as_local_time() || + (lhs.as_local_time() == rhs.as_local_time() && + lhs.comments() < rhs.comments()); + } + case value_t::array : + { + return lhs.as_array() < rhs.as_array() || + (lhs.as_array() == rhs.as_array() && + lhs.comments() < rhs.comments()); + } + case value_t::table : + { + return lhs.as_table() < rhs.as_table() || + (lhs.as_table() == rhs.as_table() && + lhs.comments() < rhs.comments()); + } + case value_t::empty : + { + return lhs.comments() < rhs.comments(); + } + default: + { + return lhs.comments() < rhs.comments(); + } + } +} + +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator<=(const basic_value& lhs, const basic_value& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator>(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs <= rhs); +} +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator>=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs < rhs); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const basic_value& v, const std::string& comment, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, {{v.location(), comment}}, + std::move(hints), colorize); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const toml::basic_value& v1, const std::string& comment1, + const toml::basic_value& v2, const std::string& comment2, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, { + {v1.location(), comment1}, {v2.location(), comment2} + }, std::move(hints), colorize); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const toml::basic_value& v1, const std::string& comment1, + const toml::basic_value& v2, const std::string& comment2, + const toml::basic_value& v3, const std::string& comment3, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, {{v1.location(), comment1}, + {v2.location(), comment2}, {v3.location(), comment3} + }, std::move(hints), colorize); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, const toml::basic_value& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(v.as_boolean ());} + case value_t::integer : {return visitor(v.as_integer ());} + case value_t::floating : {return visitor(v.as_floating ());} + case value_t::string : {return visitor(v.as_string ());} + case value_t::offset_datetime: {return visitor(v.as_offset_datetime());} + case value_t::local_datetime : {return visitor(v.as_local_datetime ());} + case value_t::local_date : {return visitor(v.as_local_date ());} + case value_t::local_time : {return visitor(v.as_local_time ());} + case value_t::array : {return visitor(v.as_array ());} + case value_t::table : {return visitor(v.as_table ());} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, toml::basic_value& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(v.as_boolean ());} + case value_t::integer : {return visitor(v.as_integer ());} + case value_t::floating : {return visitor(v.as_floating ());} + case value_t::string : {return visitor(v.as_string ());} + case value_t::offset_datetime: {return visitor(v.as_offset_datetime());} + case value_t::local_datetime : {return visitor(v.as_local_datetime ());} + case value_t::local_date : {return visitor(v.as_local_date ());} + case value_t::local_time : {return visitor(v.as_local_time ());} + case value_t::array : {return visitor(v.as_array ());} + case value_t::table : {return visitor(v.as_table ());} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, toml::basic_value&& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(std::move(v.as_boolean ()));} + case value_t::integer : {return visitor(std::move(v.as_integer ()));} + case value_t::floating : {return visitor(std::move(v.as_floating ()));} + case value_t::string : {return visitor(std::move(v.as_string ()));} + case value_t::offset_datetime: {return visitor(std::move(v.as_offset_datetime()));} + case value_t::local_datetime : {return visitor(std::move(v.as_local_datetime ()));} + case value_t::local_date : {return visitor(std::move(v.as_local_date ()));} + case value_t::local_time : {return visitor(std::move(v.as_local_time ()));} + case value_t::array : {return visitor(std::move(v.as_array ()));} + case value_t::table : {return visitor(std::move(v.as_table ()));} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +}// toml +#endif// TOML11_VALUE