From 66825d62aabdf4f47e18b179c24427f2fa804b13 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 11 Oct 2024 20:43:37 +0300 Subject: [PATCH] feat: toml arrays and inline tables support --- src/coders/toml.cpp | 85 ++++++++++++++++++++++++++++++------------- src/data/dv.hpp | 3 ++ test/coders/toml.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 test/coders/toml.cpp diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index 9dbf3e6e..b8dc1ea3 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -52,6 +52,65 @@ class TomlReader : BasicParser { } while (true); } + dv::value parseValue() { + char c = peek(); + if (is_digit(c)) { + return parseNumber(1); + } else if (c == '-' || c == '+') { + int sign = c == '-' ? -1 : 1; + pos++; + // parse numeric literal + auto value = parseNumber(sign); + if (hasNext() && peekNoJump() == '-') { + // parse timestamp // TODO: implement + throw error("timestamps support is not implemented yet"); + } + return value; + } else if (is_identifier_start(c)) { + // parse keywords + std::string keyword = parseName(); + if (keyword == "true" || keyword == "false") { + return keyword == "true"; + } else if (keyword == "inf") { + return INFINITY; + } else if (keyword == "nan") { + return NAN; + } + throw error("unknown keyword " + util::quote(keyword)); + } else if (c == '"' || c == '\'') { + pos++; + return parseString(c); + } else if (c == '[') { + // parse array + pos++; + dv::list_t values; + while (peek() != ']') { + values.push_back(parseValue()); + if (peek() != ']') { + expect(','); + } + } + pos++; + return dv::value(std::move(values)); + } else if (c == '{') { + // parse inline table + pos++; + auto table = dv::object(); + while (peek() != '}') { + auto key = parseName(); + expect('='); + table[key] = parseValue(); + if (peek() != '}') { + expect(','); + } + } + pos++; + return table; + } else { + throw error("feature is not supported"); + } + } + void readSection(const std::string& section, dv::value& map) { while (hasNext()) { skipWhitespace(); @@ -68,35 +127,13 @@ class TomlReader : BasicParser { pos--; std::string name = parseName(); expect('='); - c = peek(); - if (is_digit(c)) { - map[name] = parseNumber(1); - } else if (c == '-' || c == '+') { - int sign = c == '-' ? -1 : 1; - pos++; - map[name] = parseNumber(sign); - } else if (is_identifier_start(c)) { - std::string identifier = parseName(); - if (identifier == "true" || identifier == "false") { - map[name] = identifier == "true"; - } else if (identifier == "inf") { - map[name] = INFINITY; - } else if (identifier == "nan") { - map[name] = NAN; - } - } else if (c == '"' || c == '\'') { - pos++; - map[name] = parseString(c); - } else { - throw error("feature is not supported"); - } + map[name] = parseValue(); expectNewLine(); } } public: TomlReader(std::string_view file, std::string_view source) - : BasicParser(file, source) { - root = dv::object(); + : BasicParser(file, source), root(dv::object()) { } dv::value read() { diff --git a/src/data/dv.hpp b/src/data/dv.hpp index 369f27a0..94d96453 100644 --- a/src/data/dv.hpp +++ b/src/data/dv.hpp @@ -196,6 +196,9 @@ namespace dv { value(std::shared_ptr v) noexcept { this->operator=(std::move(v)); } + value(list_t values) { + this->operator=(std::make_shared(std::move(values))); + } value(const value& v) noexcept : type(value_type::none) { this->operator=(v); diff --git a/test/coders/toml.cpp b/test/coders/toml.cpp new file mode 100644 index 00000000..369a20cf --- /dev/null +++ b/test/coders/toml.cpp @@ -0,0 +1,86 @@ +#include "coders/toml.hpp" + +#include + +#include "data/dv.hpp" +#include "util/stringutil.hpp" +#include "util/Buffer.hpp" +#include "coders/commons.hpp" + +// Example from toml.io +inline std::string SRC_EXAMPLE = + "# This is a TOML document\n" + "\n" + "title = \"TOML Example\"\n" + "\n" + "[owner]\n" + "name = \"Tom Preston-Werner\"\n" +// "dob = 1979-05-27T07:32:00-08:00\n" + "\n" + "[database]\n" + "enabled = true\n" + "ports = [ 8000, 8001, 8002 ]\n" + "data = [ [\"delta\", \"phi\"], [3.14] ]\n" + "temp_targets = { cpu = 79.5, case = 72.0 }\n" + "\n" + "[servers]\n" + "\n" + "[servers.alpha]\n" + "ip = \"10.0.0.1\"\n" + "role = \"frontend\"\n" + "\n" + "[servers.beta]\n" + "ip = \"10.0.0.2\"\n" + "role = \"backend\""; + +TEST(TOML, EncodeDecode) { + const std::string name = "TOML-encoder"; + const int bytesSize = 20; + const int year = 2019; + const float score = 3.141592; + const bool visible = true; + dv::objects::Bytes srcBytes(bytesSize); + for (int i = 0; i < bytesSize; i ++) { + srcBytes[i] = rand(); + } + + std::string text; + { + auto object = dv::object(); + object["name"] = name; + object["year"] = year; + object["score"] = score; + object["visible"] = visible; + object["data"] = srcBytes; + + text = toml::stringify(object, ""); + std::cout << text << std::endl; + } + try { + auto object = toml::parse("", text); + EXPECT_EQ(object["name"].asString(), name); + EXPECT_EQ(object["year"].asInteger(), year); + EXPECT_FLOAT_EQ(object["score"].asNumber(), score); + EXPECT_EQ(object["visible"].asBoolean(), visible); + auto b64string = object["data"].asString(); + + auto bytes = util::base64_decode(b64string); + EXPECT_EQ(bytes.size(), bytesSize); + for (int i = 0; i < bytesSize; i++) { + EXPECT_EQ(bytes[i], srcBytes[i]); + } + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw; + } +} + +TEST(TOML, ExampleCode) { + try { + auto object = toml::parse("", SRC_EXAMPLE); + std::cout << object << std::endl; + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw; + } +}