From 594accf9a7adf5dd1b292cf7273b9df8cd6b7a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= Date: Thu, 30 Jun 2022 11:37:53 +0200 Subject: [PATCH 1/4] chore: Don't include fstream in lexer --- toml/lexer.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/toml/lexer.hpp b/toml/lexer.hpp index 5d019a3..ce150c5 100644 --- a/toml/lexer.hpp +++ b/toml/lexer.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include "combinator.hpp" From bf9c9d620dc38a9145577878271f42f7c6b3dac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= Date: Thu, 30 Jun 2022 13:53:32 +0200 Subject: [PATCH 2/4] feat: Add a parse(FILE *) interface The fstream classes are notorious for their non-existent error handling. This adds a C-style fILE * IO (fopen(), etc.) alternative interface, so that if a user needs reliable error handling, they can use that, albeit more inconvenient, but more robust approach. --- tests/test_parse_file.cpp | 74 ++++++++++++++++++++++++++++++++++++++ toml/exception.hpp | 12 +++++++ toml/parser.hpp | 75 +++++++++++++++++++++++++++++++-------- 3 files changed, 146 insertions(+), 15 deletions(-) diff --git a/tests/test_parse_file.cpp b/tests/test_parse_file.cpp index c2d3610..80d26e5 100644 --- a/tests/test_parse_file.cpp +++ b/tests/test_parse_file.cpp @@ -142,6 +142,75 @@ BOOST_AUTO_TEST_CASE(test_example_stream) } } +BOOST_AUTO_TEST_CASE(test_example_file_pointer) +{ + FILE * file = fopen("toml/tests/example.toml", "rb"); + const auto data = toml::parse(file, "toml/tests/example.toml"); + fclose(file); + + BOOST_TEST(toml::find(data, "title") == "TOML Example"); + const auto& owner = toml::find(data, "owner"); + { + BOOST_TEST(toml::find(owner, "name") == "Tom Preston-Werner"); + BOOST_TEST(toml::find(owner, "organization") == "GitHub"); + BOOST_TEST(toml::find(owner, "bio") == + "GitHub Cofounder & CEO\nLikes tater tots and beer."); + BOOST_TEST(toml::find(owner, "dob") == + toml::offset_datetime(toml::local_date(1979, toml::month_t::May, 27), + toml::local_time(7, 32, 0), toml::time_offset(0, 0))); + } + + const auto& database = toml::find(data, "database"); + { + BOOST_TEST(toml::find(database, "server") == "192.168.1.1"); + const std::vector expected_ports{8001, 8001, 8002}; + BOOST_CHECK(toml::find>(database, "ports") == expected_ports); + BOOST_TEST(toml::find(database, "connection_max") == 5000); + BOOST_TEST(toml::find(database, "enabled") == true); + } + + const auto& servers = toml::find(data, "servers"); + { + toml::table alpha = toml::find(servers, "alpha"); + BOOST_TEST(toml::get(alpha.at("ip")) == "10.0.0.1"); + BOOST_TEST(toml::get(alpha.at("dc")) == "eqdc10"); + + toml::table beta = toml::find(servers, "beta"); + BOOST_TEST(toml::get(beta.at("ip")) == "10.0.0.2"); + BOOST_TEST(toml::get(beta.at("dc")) == "eqdc10"); + BOOST_TEST(toml::get(beta.at("country")) == "\xE4\xB8\xAD\xE5\x9B\xBD"); + } + + const auto& clients = toml::find(data, "clients"); + { + toml::array clients_data = toml::find(clients, "data"); + std::vector expected_name{"gamma", "delta"}; + BOOST_CHECK(toml::get>(clients_data.at(0)) == expected_name); + + std::vector expected_number{1, 2}; + BOOST_CHECK(toml::get>(clients_data.at(1)) == expected_number); + + std::vector expected_hosts{"alpha", "omega"}; + BOOST_CHECK(toml::find>(clients, "hosts") == expected_hosts); + } + + std::vector products = + toml::find>(data, "products"); + { + BOOST_TEST(toml::get(products.at(0).at("name")) == + "Hammer"); + BOOST_TEST(toml::get(products.at(0).at("sku")) == + 738594937); + + BOOST_TEST(toml::get(products.at(1).at("name")) == + "Nail"); + BOOST_TEST(toml::get(products.at(1).at("sku")) == + 284758393); + BOOST_TEST(toml::get(products.at(1).at("color")) == + "gray"); + } +} + BOOST_AUTO_TEST_CASE(test_fruit) { const auto data = toml::parse("toml/tests/fruit.toml"); @@ -959,3 +1028,8 @@ BOOST_AUTO_TEST_CASE(test_parse_function_compiles) BOOST_TEST_MESSAGE("path"); #endif } + +BOOST_AUTO_TEST_CASE(test_parse_nonexistent_file) +{ + BOOST_CHECK_THROW(toml::parse("nonexistent.toml"), std::runtime_error); +} diff --git a/toml/exception.hpp b/toml/exception.hpp index c64651d..c219216 100644 --- a/toml/exception.hpp +++ b/toml/exception.hpp @@ -10,6 +10,18 @@ namespace toml { +struct file_io_error : public std::runtime_error +{ + public: + file_io_error(int errnum, const std::string& msg, const std::string& fname) + : std::runtime_error(msg + " \"" + fname + "\": " + std::strerror(errnum)), + errno_(errnum) + {} + int get_errno() {return errno_;} + private: + int errno_; +}; + struct exception : public std::exception { public: diff --git a/toml/parser.hpp b/toml/parser.hpp index fe952bf..27589e9 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -2377,27 +2377,14 @@ result parse_toml_file(location& loc) 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, std::string fname = "unknown file") +parse(std::vector& letters, const std::string& fname) { 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); - // append LF. // Although TOML does not require LF at the EOF, to make parsing logic // simpler, we "normalize" the content by adding LF if it does not exist. @@ -2435,12 +2422,70 @@ parse(std::istream& is, std::string fname = "unknown file") return data.unwrap(); } +} // detail + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value +parse(FILE * file, const std::string& fname) +{ + const long beg = std::ftell(file); + if (beg == -1l) { + throw file_io_error(errno, "Failed to access", fname); + } + + int res = std::fseek(file, 0, SEEK_END); + if (res != 0) { + throw file_io_error(errno, "Failed to seek", fname); + } + + const long end = std::ftell(file); + if (end == -1l) { + throw file_io_error(errno, "Failed to access", fname); + } + + const auto fsize = end - beg; + + res = std::fseek(file, beg, SEEK_SET); + if (res != 0) { + throw file_io_error(errno, "Failed to seek", fname); + } + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + std::fread(letters.data(), sizeof(char), static_cast(fsize), file); + + return detail::parse(letters, fname); +} + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value +parse(std::istream& is, std::string fname = "unknown file") +{ + 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); + + return detail::parse(letters, fname); +} + template class Table = std::unordered_map, template class Array = std::vector> basic_value parse(std::string fname) { - std::ifstream ifs(fname.c_str(), std::ios_base::binary); + std::ifstream ifs(fname, std::ios_base::binary); if(!ifs.good()) { throw std::runtime_error("toml::parse: file open error -> " + fname); From 021d84623c86aae1f8bb90887ff4fe7f43515678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= Date: Thu, 8 Sep 2022 16:52:46 +0200 Subject: [PATCH 3/4] chore: De-duplicate code for parse(std::filesystem::path) --- toml/parser.hpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/toml/parser.hpp b/toml/parser.hpp index 27589e9..9b70feb 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -2516,13 +2516,7 @@ 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()); + return parse(fpath.string()); } #endif // TOML11_HAS_STD_FILESYSTEM From 6c2c804effd5e37108b7f4720a8e755786681d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= Date: Thu, 8 Sep 2022 16:58:56 +0200 Subject: [PATCH 4/4] fix: Improve error handling of ifstream a bit Set the exceptions mask so that exceptions are thrown when an I/O error occurs. Also throw the same exception type when the opening fails. --- tests/test_parse_file.cpp | 2 +- toml/parser.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_parse_file.cpp b/tests/test_parse_file.cpp index 80d26e5..b525f98 100644 --- a/tests/test_parse_file.cpp +++ b/tests/test_parse_file.cpp @@ -1031,5 +1031,5 @@ BOOST_AUTO_TEST_CASE(test_parse_function_compiles) BOOST_AUTO_TEST_CASE(test_parse_nonexistent_file) { - BOOST_CHECK_THROW(toml::parse("nonexistent.toml"), std::runtime_error); + BOOST_CHECK_THROW(toml::parse("nonexistent.toml"), std::ios_base::failure); } diff --git a/toml/parser.hpp b/toml/parser.hpp index 9b70feb..ce93554 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -2488,8 +2488,9 @@ basic_value parse(std::string fname) std::ifstream ifs(fname, std::ios_base::binary); if(!ifs.good()) { - throw std::runtime_error("toml::parse: file open error -> " + fname); + throw std::ios_base::failure("toml::parse: Error opening file \"" + fname + "\""); } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); return parse(ifs, std::move(fname)); }