diff --git a/.circleci/config.yml b/.circleci/config.yml index 0867ff4..6a68cfa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: command: | g++ --version cd tests/ - g++ -std=c++11 -O2 -Wall -Wextra -Werror -DTOML11_COLORIZE_ERROR_MESSAGE -I../ check_toml_test.cpp -o check_toml_test + g++ -std=c++11 -O2 -Wall -Wextra -Werror -DTOML11_DISALLOW_HETEROGENEOUS_ARRAYS -I../ check_toml_test.cpp -o check_toml_test go get github.com/BurntSushi/toml-test $GOPATH/bin/toml-test ./check_toml_test test_serialization: @@ -24,7 +24,7 @@ jobs: command: | g++ --version cd tests/ - g++ -std=c++11 -O2 -Wall -Wextra -Wpedantic -Werror -DTOML11_COLORIZE_ERROR_MESSAGE -I../ check_serialization.cpp -o check_serialization + g++ -std=c++11 -O2 -Wall -Wextra -Wpedantic -Werror -DTOML11_DISALLOW_HETEROGENEOUS_ARRAYS -I../ check_serialization.cpp -o check_serialization git clone https://github.com/BurntSushi/toml-test.git cp check_serialization toml-test/tests/valid cd toml-test/tests/valid @@ -47,7 +47,7 @@ jobs: command: | g++ --version cd tests/ - g++ -std=c++11 -O2 -Wall -Wextra -Wpedantic -Werror -DTOML11_COLORIZE_ERROR_MESSAGE -I../ check.cpp -o check + g++ -std=c++11 -O2 -Wall -Wextra -Wpedantic -Werror -DTOML11_DISALLOW_HETEROGENEOUS_ARRAYS -I../ check.cpp -o check git clone https://github.com/BurntSushi/toml-test.git cp check toml-test/tests/invalid cp check toml-test/tests/valid diff --git a/README.md b/README.md index 3823a0d..9629ebd 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ toml11 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 v0.5.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md). -- It optionally supports the [unreleased features](#unreleased-toml-features) in the master branch of toml-lang/toml. +- It is compatible to the latest version of [TOML v1.0.0-rc.1](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v1.0.0-rc.1.md). - 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. @@ -120,6 +119,9 @@ 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. @@ -177,7 +179,7 @@ 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 ``` @@ -267,11 +269,11 @@ shape = "round" ``` cpp const auto data = toml::parse("fruit.toml"); const auto& fruit = toml::find(data, "fruit"); -const auto name = toml::find(fruit, "apple"); +const auto name = toml::find(fruit, "name"); const auto& physical = toml::find(fruit, "physical"); -const auto color = toml::find(fruit, "color"); -const auto shape = toml::find(fruit, "shape"); +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 @@ -463,6 +465,24 @@ if(answer.is_integer() && answer.as_integer(std::nothrow) == 42) 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 @@ -1652,13 +1672,8 @@ not capable of representing a Local Time independent from a specific day. ## Unreleased TOML features -There are some unreleased features in toml-lang/toml:master. -Currently, the following features are available after defining -`TOML11_USE_UNRELEASED_TOML_FEATURES` macro flag. - -To use those features, `#define` `TOML11_USE_UNRELEASED_TOML_FEATURES` before -including `toml.hpp` or pass `-DTOML11_USE_UNRELEASED_TOML_FEATURES` to your -compiler. +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` @@ -1668,10 +1683,10 @@ compiler. - Allow heterogeneous arrays - [toml-lang/toml/PR/676](https://github.com/toml-lang/toml/pull/676) -### Note about heterogeneous arrays +## Note about heterogeneous arrays Although `toml::parse` allows heterogeneous arrays, constructor of `toml::value` -does not. +does not. Here the reason is explained. ```cpp // this won't be compiled @@ -1680,8 +1695,10 @@ toml::value v{ } ``` -There is a workaround for this issue. By explicitly converting values into +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! @@ -1689,6 +1706,17 @@ 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. @@ -1717,15 +1745,14 @@ This means that the above C++ code makes constructor's overload resolution ambiguous. So a constructor that allows both "table as an initializer-list" and "heterogeneous array as an initializer-list" cannot be implemented. -Thus, although it is painful, you need to explicitly cast values into -`toml::value` when you initialize heterogeneous array in C++ code. +Thus, although it is painful, we need to explicitly cast values into +`toml::value` when you initialize heterogeneous array in a C++ code. ```cpp -// You need to do this when you want to initialize hetero array. toml::value v{ toml::value("foo"), toml::value(3.14), toml::value(42), toml::value{1,2,3,4,5}, toml::value{{"key", "value"}} -} +}; ``` ## Breaking Changes from v2 diff --git a/tests/test_error_detection.cpp b/tests/test_error_detection.cpp index 49dff6e..07c607f 100644 --- a/tests/test_error_detection.cpp +++ b/tests/test_error_detection.cpp @@ -80,13 +80,13 @@ BOOST_AUTO_TEST_CASE(test_detect_conflicting_value) BOOST_AUTO_TEST_CASE(test_detect_inhomogeneous_array) { -#ifdef TOML11_USE_UNRELEASED_TOML_FEATURES - BOOST_TEST_MESSAGE("heterogeneous array will be allowed in the next release"); -#else +#ifdef TOML11_DISALLOW_HETEROGENEOUS_ARRAYS std::istringstream stream(std::string( "a = [1, 1.0]\n" )); BOOST_CHECK_THROW(toml::parse(stream), toml::syntax_error); +#else + BOOST_TEST_MESSAGE("After v1.0.0-rc.1, heterogeneous arrays are allowed"); #endif } diff --git a/toml/lexer.hpp b/toml/lexer.hpp index c014fd8..2eea996 100644 --- a/toml/lexer.hpp +++ b/toml/lexer.hpp @@ -70,15 +70,8 @@ using lex_zero_prefixable_int = sequence, lex_zero_prefixable_int>; -#ifdef TOML11_USE_UNRELEASED_TOML_FEATURES -// use toml-lang/toml HEAD using lex_exponent_part = sequence, character<'E'>>, maybe, lex_zero_prefixable_int>; -#else -// strictly TOML v0.5.0 -using lex_exponent_part = sequence, character<'E'>>, - lex_dec_int>; -#endif using lex_float = either; -#ifdef TOML11_USE_UNRELEASED_TOML_FEATURES using lex_basic_unescaped = exclude, // 0x09 (tab) in_range<0x0a, 0x1F>, // is allowed character<0x22>, character<0x5C>, character<0x7F>>>; -#else -using lex_basic_unescaped = exclude, - character<0x22>, character<0x5C>, - character<0x7F>>>; -#endif using lex_escape = character<'\\'>; using lex_escape_unicode_short = sequence, repeat>>; @@ -184,12 +171,6 @@ using lex_basic_string = sequence>; using lex_ml_basic_string_open = lex_ml_basic_string_delim; using lex_ml_basic_string_close = sequence< @@ -197,18 +178,11 @@ using lex_ml_basic_string_close = sequence< maybe, maybe >; -#ifdef TOML11_USE_UNRELEASED_TOML_FEATURES using lex_ml_basic_unescaped = exclude, // 0x09 in_range<0x0a, 0x1F>, // is tab character<0x5C>, // backslash character<0x7F>, // DEL lex_ml_basic_string_delim>>; -#else // TOML v0.5.0 -using lex_ml_basic_unescaped = exclude, - character<0x5C>, - character<0x7F>, - lex_ml_basic_string_delim>>; -#endif using lex_ml_basic_escaped_newline = sequence< lex_escape, maybe, lex_newline, diff --git a/toml/parser.hpp b/toml/parser.hpp index da14393..589add9 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -13,6 +13,13 @@ #include "types.hpp" #include "value.hpp" +#if __cplusplus >= 201703L +#if __has_include() +#define TOML11_HAS_STD_FILESYSTEM +#include +#endif // has_include() +#endif // cplusplus >= C++17 + namespace toml { namespace detail @@ -734,7 +741,13 @@ parse_local_time(location& loc) case 0: break; default: break; } - if(sf.size() >= 6) + 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); @@ -978,7 +991,12 @@ parse_array(location& loc) if(auto val = parse_value(loc)) { -#ifndef TOML11_USE_UNRELEASED_TOML_FEATURES + // 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; @@ -1390,9 +1408,6 @@ insert_nested_key(typename Value::table_type& root, const Value& v, // According to toml-lang/toml:36d3091b3 "Clarify that inline // tables are immutable", check if it adds key-value pair to an // inline table. - // This is one of the unreleased (after-0.5.0) toml feature. - // But this is marked as "Clarify", so TOML-lang intended that - // inline tables are immutable in all version. { // here, if the value is a (multi-line) table, the region // should be something like `[table-name]`. @@ -2095,5 +2110,39 @@ basic_value parse(const std::string& 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 string literal. +// So this function is preferred than others and the ambiguity disappears. +template class Table = std::unordered_map, + template class Array = std::vector, + std::size_t N> +basic_value parse(const char (&fname)[N]) +{ + 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/toml/serializer.hpp b/toml/serializer.hpp index a578c5f..e090aa2 100644 --- a/toml/serializer.hpp +++ b/toml/serializer.hpp @@ -137,43 +137,8 @@ struct serializer { // the resulting value does not have any float specific part! token += ".0"; - return token; - } - if(!has_exponent) - { - return token; // there is no exponent part. just return it. - } -#ifdef TOML11_USE_UNRELEASED_TOML_FEATURES - // Although currently it is not released yet as a tagged version, - // TOML will allow zero-prefix in an exponent part, such as `1.234e+01`. - // ```toml - // num1 = 1.234e+1 # OK in TOML v0.5.0 - // num2 = 1.234e+01 # error in TOML v0.5.0 but will be allowed soon - // ``` - // To avoid `e+01`, the following `else` section removes the zero - // prefixes in the exponent part. - // If the feature is activated, it can be skipped. - return token; -#else - // zero-prefix in an exponent is NOT allowed in TOML v0.5.0. - // remove it if it exists. - bool sign_exists = false; - std::size_t zero_prefix = 0; - for(auto iter = std::next(e), iend = token.cend(); iter != iend; ++iter) - { - if(*iter == '+' || *iter == '-'){sign_exists = true; continue;} - if(*iter == '0'){zero_prefix += 1;} - else {break;} - } - if(zero_prefix != 0) - { - const auto offset = std::distance(token.cbegin(), e) + - (sign_exists ? 2 : 1); - token.erase(static_cast(offset), - zero_prefix); } return token; -#endif } std::string operator()(const string_type& s) const { @@ -734,7 +699,8 @@ format(const basic_value& v, std::size_t w = 80u, oss << v.comments(); oss << '\n'; // to split the file comment from the first element } - oss << visit(serializer(w, fprec, no_comment, false), v); + const auto serialized = visit(serializer(w, fprec, no_comment, false), v); + oss << serialized; return oss.str(); } return visit(serializer(w, fprec, force_inline), v); @@ -790,7 +756,8 @@ operator<<(std::basic_ostream& os, const basic_value& v) os << '\n'; // to split the file comment from the first element } // the root object can't be an inline table. so pass `false`. - os << visit(serializer(w, fprec, false, no_comment), v); + 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.