From 66825d62aabdf4f47e18b179c24427f2fa804b13 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 11 Oct 2024 20:43:37 +0300 Subject: [PATCH 1/7] 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; + } +} From d14548cff8cfcf3659ac19d07e7884894edb1e6f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 12 Oct 2024 00:23:52 +0300 Subject: [PATCH 2/7] feat: toml lvalues support --- src/coders/commons.cpp | 16 ++++++++--- src/coders/commons.hpp | 13 +++++++-- src/coders/json.cpp | 3 +- src/coders/toml.cpp | 64 ++++++++++++++++++++---------------------- src/data/dv.cpp | 2 +- src/engine.cpp | 8 +++++- src/window/Events.cpp | 35 ++++++++++++----------- 7 files changed, 83 insertions(+), 58 deletions(-) diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index e915b6a6..8f2ca6cd 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -221,10 +221,6 @@ std::string_view BasicParser::readUntilEOL() { std::string BasicParser::parseName() { char c = peek(); if (!is_identifier_start(c)) { - if (c == '"') { - pos++; - return parseString(c); - } throw error("identifier expected"); } int start = pos; @@ -234,6 +230,18 @@ std::string BasicParser::parseName() { return std::string(source.substr(start, pos - start)); } +std::string BasicParser::parseXmlName() { + char c = peek(); + if (!is_json_identifier_start(c)) { + throw error("identifier expected"); + } + int start = pos; + while (hasNext() && is_json_identifier_part(source[pos])) { + pos++; + } + return std::string(source.substr(start, pos - start)); +} + int64_t BasicParser::parseSimpleInt(int base) { char c = peek(); int index = hexchar2int(c); diff --git a/src/coders/commons.hpp b/src/coders/commons.hpp index e5bb3bc1..952ea316 100644 --- a/src/coders/commons.hpp +++ b/src/coders/commons.hpp @@ -30,14 +30,22 @@ inline bool is_whitespace(int c) { } inline bool is_identifier_start(int c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || - c == '.'; + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; } inline bool is_identifier_part(int c) { return is_identifier_start(c) || is_digit(c) || c == '-'; } +inline bool is_json_identifier_start(int c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || + c == '.'; +} + +inline bool is_json_identifier_part(int c) { + return is_json_identifier_start(c) || is_digit(c) || c == '-'; +} + inline int hexchar2int(int c) { if (c >= '0' && c <= '9') { return c - '0'; @@ -99,6 +107,7 @@ public: std::string_view readUntil(char c); std::string_view readUntilEOL(); std::string parseName(); + std::string parseXmlName(); bool hasNext(); char peek(); char peekInLine(); diff --git a/src/coders/json.cpp b/src/coders/json.cpp index 8fb6a1e2..9c189998 100644 --- a/src/coders/json.cpp +++ b/src/coders/json.cpp @@ -178,7 +178,8 @@ dv::value Parser::parseObject() { skipLine(); continue; } - std::string key = parseName(); + expect('"'); + std::string key = parseString('"'); char next = peek(); if (next != ':') { throw error("':' expected"); diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index b8dc1ea3..1bbb52aa 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -26,32 +26,6 @@ class TomlReader : BasicParser { } } - dv::value& getSection(const std::string& section) { - if (section.empty()) { - return root; - } - size_t offset = 0; - auto rootMap = &root; - do { - size_t index = section.find('.', offset); - if (index == std::string::npos) { - auto map = rootMap->at(section); - if (!map) { - return rootMap->object(section); - } - return *map; - } - auto subsection = section.substr(offset, index); - auto map = rootMap->at(subsection); - if (!map) { - rootMap = &rootMap->object(subsection); - } else { - rootMap = &*map; - } - offset = index + 1; - } while (true); - } - dv::value parseValue() { char c = peek(); if (is_digit(c)) { @@ -111,7 +85,30 @@ class TomlReader : BasicParser { } } - void readSection(const std::string& section, dv::value& map) { + dv::value& parseLValue(dv::value& root) { + dv::value* lvalue = &root; + while (hasNext()) { + char c = peek(); + std::string name; + if (c == '\'' || c == '"') { + pos++; + name = parseString(c); + } else { + name = parseName(); + } + if (lvalue->getType() == dv::value_type::none) { + *lvalue = dv::object(); + } + lvalue = &(*lvalue)[name]; + if (peek() != '.') { + break; + } + pos++; + } + return *lvalue; + } + + void readSection(dv::value& map, dv::value& root) { while (hasNext()) { skipWhitespace(); if (!hasNext()) { @@ -119,15 +116,15 @@ class TomlReader : BasicParser { } char c = nextChar(); if (c == '[') { - std::string name = parseName(); - pos++; - readSection(name, getSection(name)); + dv::value& section = parseLValue(root); + expect(']'); + readSection(section, root); return; } pos--; - std::string name = parseName(); + dv::value& lvalue = parseLValue(map); expect('='); - map[name] = parseValue(); + lvalue = parseValue(); expectNewLine(); } } @@ -141,7 +138,7 @@ public: if (!hasNext()) { return std::move(root); } - readSection("", root); + readSection(root, root); return std::move(root); } }; @@ -150,6 +147,7 @@ void toml::parse( SettingsHandler& handler, std::string_view file, std::string_view source ) { auto map = parse(file, source); + for (const auto& [sectionName, sectionMap] : map.asObject()) { if (!sectionMap.isObject()) { continue; diff --git a/src/data/dv.cpp b/src/data/dv.cpp index 5709fd84..16502c04 100644 --- a/src/data/dv.cpp +++ b/src/data/dv.cpp @@ -157,5 +157,5 @@ namespace dv { #include "coders/json.hpp" std::ostream& operator<<(std::ostream& stream, const dv::value& value) { - return stream << json::stringify(value, false); + return stream << json::stringify(value, true); } diff --git a/src/engine.cpp b/src/engine.cpp index 787a257b..8a3b3147 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -9,6 +9,7 @@ #include "coders/imageio.hpp" #include "coders/json.hpp" #include "coders/toml.hpp" +#include "coders/commons.hpp" #include "content/Content.hpp" #include "content/ContentBuilder.hpp" #include "content/ContentLoader.hpp" @@ -125,7 +126,12 @@ void Engine::loadSettings() { if (fs::is_regular_file(settings_file)) { logger.info() << "loading settings"; std::string text = files::read_string(settings_file); - toml::parse(settingsHandler, settings_file.string(), text); + try { + toml::parse(settingsHandler, settings_file.string(), text); + } catch (const parsing_error& err) { + logger.error() << err.errorLog(); + throw; + } } } diff --git a/src/window/Events.cpp b/src/window/Events.cpp index 67c305cf..092e4a0d 100644 --- a/src/window/Events.cpp +++ b/src/window/Events.cpp @@ -196,22 +196,25 @@ void Events::loadBindings( const std::string& filename, const std::string& source ) { auto map = toml::parse(filename, source); - for (auto& [key, value] : map.asObject()) { - auto [prefix, codename] = util::split_at(value.asString(), ':'); - inputtype type; - int code; - if (prefix == "key") { - type = inputtype::keyboard; - code = static_cast(input_util::keycode_from(codename)); - } else if (prefix == "mouse") { - type = inputtype::mouse; - code = static_cast(input_util::mousecode_from(codename)); - } else { - logger.error() - << "unknown input type: " << prefix << " (binding " - << util::quote(key) << ")"; - continue; + for (auto& [sectionName, section] : map.asObject()) { + for (auto& [name, value] : section.asObject()) { + auto key = sectionName + "." + name; + auto [prefix, codename] = util::split_at(value.asString(), ':'); + inputtype type; + int code; + if (prefix == "key") { + type = inputtype::keyboard; + code = static_cast(input_util::keycode_from(codename)); + } else if (prefix == "mouse") { + type = inputtype::mouse; + code = static_cast(input_util::mousecode_from(codename)); + } else { + logger.error() + << "unknown input type: " << prefix << " (binding " + << util::quote(key) << ")"; + continue; + } + Events::bind(key, type, code); } - Events::bind(key, type, code); } } From abe004c3d57692f31f5c31c907d3efc8f52e90a9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 12 Oct 2024 17:37:06 +0300 Subject: [PATCH 3/7] revert operator<< overload of dv::value --- src/data/dv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/dv.cpp b/src/data/dv.cpp index 16502c04..5709fd84 100644 --- a/src/data/dv.cpp +++ b/src/data/dv.cpp @@ -157,5 +157,5 @@ namespace dv { #include "coders/json.hpp" std::ostream& operator<<(std::ostream& stream, const dv::value& value) { - return stream << json::stringify(value, true); + return stream << json::stringify(value, false); } From 2f7fbd57ee7eda463c56fc9d0f5fc5e6d6f99855 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 12 Oct 2024 23:07:10 +0300 Subject: [PATCH 4/7] feat: toml multiline string test --- src/coders/commons.cpp | 44 +++++++++++------------------------ src/coders/commons.hpp | 1 + src/coders/toml.cpp | 53 +++++++++++++++++++++++++++++++++++++++++- test/coders/toml.cpp | 53 +++++++++++++++++++++--------------------- 4 files changed, 94 insertions(+), 57 deletions(-) diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index 8f2ca6cd..5c05626b 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -108,6 +108,10 @@ bool BasicParser::hasNext() { return pos < source.length(); } +size_t BasicParser::remain() const { + return source.length() - pos; +} + bool BasicParser::isNext(const std::string& substring) { if (source.length() - pos < substring.length()) { return false; @@ -357,36 +361,16 @@ std::string BasicParser::parseString(char quote, bool closeRequired) { continue; } switch (c) { - case 'n': - ss << '\n'; - break; - case 'r': - ss << '\r'; - break; - case 'b': - ss << '\b'; - break; - case 't': - ss << '\t'; - break; - case 'f': - ss << '\f'; - break; - case '\'': - ss << '\\'; - break; - case '"': - ss << '"'; - break; - case '\\': - ss << '\\'; - break; - case '/': - ss << '/'; - break; - case '\n': - pos++; - continue; + case 'n': ss << '\n'; break; + case 'r': ss << '\r'; break; + case 'b': ss << '\b'; break; + case 't': ss << '\t'; break; + case 'f': ss << '\f'; break; + case '\'': ss << '\\'; break; + case '"': ss << '"'; break; + case '\\': ss << '\\'; break; + case '/': ss << '/'; break; + case '\n': pos++; continue; default: throw error( "'\\" + std::string({c}) + "' is an illegal escape" diff --git a/src/coders/commons.hpp b/src/coders/commons.hpp index 952ea316..abd4c01a 100644 --- a/src/coders/commons.hpp +++ b/src/coders/commons.hpp @@ -109,6 +109,7 @@ public: std::string parseName(); std::string parseXmlName(); bool hasNext(); + size_t remain() const; char peek(); char peekInLine(); char peekNoJump(); diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index 1bbb52aa..08e65720 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -26,6 +26,51 @@ class TomlReader : BasicParser { } } + std::string parseMultilineString() { + pos += 2; + char next = peek(); + + std::stringstream ss; + while (hasNext()) { + char c = source[pos]; + if (c == '"' && remain() >= 2 && + source[pos+1] == '"' && + source[pos+2] == '"') { + pos += 3; + return ss.str(); + } + if (c == '\\') { + pos++; + c = nextChar(); + if (c >= '0' && c <= '7') { + pos--; + ss << (char)parseSimpleInt(8); + continue; + } + switch (c) { + case 'n': ss << '\n'; break; + case 'r': ss << '\r'; break; + case 'b': ss << '\b'; break; + case 't': ss << '\t'; break; + case 'f': ss << '\f'; break; + case '\'': ss << '\\'; break; + case '"': ss << '"'; break; + case '\\': ss << '\\'; break; + case '/': ss << '/'; break; + case '\n': pos++; continue; + default: + throw error( + "'\\" + std::string({c}) + "' is an illegal escape" + ); + } + continue; + } + ss << c; + pos++; + } + throw error("unexpected end"); + } + dv::value parseValue() { char c = peek(); if (is_digit(c)) { @@ -53,6 +98,12 @@ class TomlReader : BasicParser { throw error("unknown keyword " + util::quote(keyword)); } else if (c == '"' || c == '\'') { pos++; + if (remain() >= 2 && + c == '"' && + source[pos] == '"' && + source[pos+1] == '"') { + return parseMultilineString(); + } return parseString(c); } else if (c == '[') { // parse array @@ -125,7 +176,7 @@ class TomlReader : BasicParser { dv::value& lvalue = parseLValue(map); expect('='); lvalue = parseValue(); - expectNewLine(); + skipWhitespace(); } } public: diff --git a/test/coders/toml.cpp b/test/coders/toml.cpp index 369a20cf..f9c189db 100644 --- a/test/coders/toml.cpp +++ b/test/coders/toml.cpp @@ -7,32 +7,6 @@ #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; @@ -75,8 +49,35 @@ TEST(TOML, EncodeDecode) { } } +// Modified 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, ExampleCode) { try { + std::cout << SRC_EXAMPLE << std::endl; auto object = toml::parse("", SRC_EXAMPLE); std::cout << object << std::endl; } catch (const parsing_error& err) { From 9667b814387cdbec832dd45fd767d0d134e90194 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Oct 2024 10:16:17 +0300 Subject: [PATCH 5/7] fix: string literal line separator escape --- src/coders/commons.cpp | 2 +- src/coders/toml.cpp | 4 +++- test/coders/toml.cpp | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index 5c05626b..b79dc14e 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -370,7 +370,7 @@ std::string BasicParser::parseString(char quote, bool closeRequired) { case '"': ss << '"'; break; case '\\': ss << '\\'; break; case '/': ss << '/'; break; - case '\n': pos++; continue; + case '\n': continue; default: throw error( "'\\" + std::string({c}) + "' is an illegal escape" diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index 08e65720..b41269e8 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -26,6 +26,8 @@ class TomlReader : BasicParser { } } + // modified version of BaseParser.parseString + // todo: extract common part std::string parseMultilineString() { pos += 2; char next = peek(); @@ -57,7 +59,7 @@ class TomlReader : BasicParser { case '"': ss << '"'; break; case '\\': ss << '\\'; break; case '/': ss << '/'; break; - case '\n': pos++; continue; + case '\n': continue; default: throw error( "'\\" + std::string({c}) + "' is an illegal escape" diff --git a/test/coders/toml.cpp b/test/coders/toml.cpp index f9c189db..a45e5286 100644 --- a/test/coders/toml.cpp +++ b/test/coders/toml.cpp @@ -73,7 +73,8 @@ inline std::string SRC_EXAMPLE = "\n" "[servers.beta]\n" "ip = \"10.0.0.2\"\n" - "role = \"\"\"backend\"\"\""; + "role = \"\"\"back\\\n" + "end\"\"\""; TEST(TOML, ExampleCode) { try { From 68c3a646c8b57ee4b7fb6aa7a761bca54163eb19 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Oct 2024 10:51:24 +0300 Subject: [PATCH 6/7] feat: local date-time, offset date-time, local time, local date support (reading to string) --- src/coders/toml.cpp | 25 +++++++++++++++++-------- test/coders/toml.cpp | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index b41269e8..468e94b6 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -76,17 +76,26 @@ class TomlReader : BasicParser { dv::value parseValue() { char c = peek(); if (is_digit(c)) { - return parseNumber(1); + int start = pos; + // parse numeric literal + auto value = parseNumber(1); + if (hasNext() && peekNoJump() == '-') { + while (hasNext()) { + c = source[pos]; + if (!is_digit(c) && c != ':' && c != '.' && c != '-' && + c != 'T' && c != 'Z') { + break; + } + pos++; + } + return std::string(source.substr(start, pos - start)); + } + return value; } 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; + return parseNumber(sign); } else if (is_identifier_start(c)) { // parse keywords std::string keyword = parseName(); @@ -178,7 +187,7 @@ class TomlReader : BasicParser { dv::value& lvalue = parseLValue(map); expect('='); lvalue = parseValue(); - skipWhitespace(); + expectNewLine(); } } public: diff --git a/test/coders/toml.cpp b/test/coders/toml.cpp index a45e5286..fa491ba3 100644 --- a/test/coders/toml.cpp +++ b/test/coders/toml.cpp @@ -57,7 +57,7 @@ inline std::string SRC_EXAMPLE = "\n" "[owner]\n" "name = \"Tom Preston-Werner\"\n" -// "dob = 1979-05-27T07:32:00-08:00\n" + "dob = 1979-05-27T07:32:00-08:00\n" "\n" "[database]\n" "enabled = true\n" From 6a2b2062b3ea1f3a3dad8de9697017005a325755 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Oct 2024 12:09:56 +0300 Subject: [PATCH 7/7] feat: array of tables support --- src/coders/toml.cpp | 22 ++++++++++++++++++++++ test/coders/toml.cpp | 9 ++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index 468e94b6..080df90e 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -178,7 +178,29 @@ class TomlReader : BasicParser { } char c = nextChar(); if (c == '[') { + if (hasNext() && peek() == '[') { + pos++; + // parse list of tables + dv::value& list = parseLValue(root); + if (list == nullptr) { + list = dv::list(); + } else if (!list.isList()) { + throw error("target is not an array"); + } + expect(']'); + expect(']'); + dv::value section = dv::object(); + readSection(section, root); + list.add(std::move(section)); + return; + } + // parse table dv::value& section = parseLValue(root); + if (section == nullptr) { + section = dv::object(); + } else if (!section.isObject()) { + throw error("target is not a table"); + } expect(']'); readSection(section, root); return; diff --git a/test/coders/toml.cpp b/test/coders/toml.cpp index fa491ba3..e8a63f19 100644 --- a/test/coders/toml.cpp +++ b/test/coders/toml.cpp @@ -74,7 +74,14 @@ inline std::string SRC_EXAMPLE = "[servers.beta]\n" "ip = \"10.0.0.2\"\n" "role = \"\"\"back\\\n" - "end\"\"\""; + "end\"\"\"\n" + "\n" + "[[users]]\n" + "name = \"noname\"\n" + "\n" + "[[users]]\n" + "name = \"user1\"\n" + "suspended = true\n"; TEST(TOML, ExampleCode) { try {