diff options
-rw-r--r-- | src/core/common/CharReader.cpp | 12 | ||||
-rw-r--r-- | src/core/common/CharReader.hpp | 9 | ||||
-rw-r--r-- | src/core/common/Utils.cpp | 4 | ||||
-rw-r--r-- | src/core/common/Utils.hpp | 16 | ||||
-rw-r--r-- | src/core/common/VariantReader.hpp | 1 | ||||
-rw-r--r-- | src/plugins/plain/PlainFormatStreamReader.cpp | 73 | ||||
-rw-r--r-- | src/plugins/plain/PlainFormatStreamReader.hpp | 42 | ||||
-rw-r--r-- | test/core/common/UtilsTest.cpp | 2 | ||||
-rw-r--r-- | test/plugins/plain/PlainFormatStreamReaderTest.cpp | 129 |
9 files changed, 276 insertions, 12 deletions
diff --git a/src/core/common/CharReader.cpp b/src/core/common/CharReader.cpp index 5b9b1d4..4d3638c 100644 --- a/src/core/common/CharReader.cpp +++ b/src/core/common/CharReader.cpp @@ -468,6 +468,18 @@ bool CharReader::read(char &c) return res; } +bool CharReader::expect(char c) +{ + char actual = 0; + peek(actual); + if (c == actual) { + consumePeek(); + return true; + } + resetPeek(); + return false; +} + void CharReader::resetPeek() { if (!coherent) { diff --git a/src/core/common/CharReader.hpp b/src/core/common/CharReader.hpp index cbfeaf2..64c80af 100644 --- a/src/core/common/CharReader.hpp +++ b/src/core/common/CharReader.hpp @@ -490,6 +490,15 @@ public: bool read(char &c); /** + * Peeks a character, checks whether this character equals the given + * character -- and if yes -- consumes the peek, otherwise resets it. + * + * @param c is the character that is expected. + * @return true if this character is actually next. + */ + bool expect(char c); + + /** * Resets the peek pointer to the "read" pointer. */ void resetPeek(); diff --git a/src/core/common/Utils.cpp b/src/core/common/Utils.cpp index e5e2d39..563fe2a 100644 --- a/src/core/common/Utils.cpp +++ b/src/core/common/Utils.cpp @@ -35,10 +35,10 @@ bool Utils::isIdentifier(const std::string &name) { bool first = true; for (char c : name) { - if (first && !(isAlphabetic(c) || c == '_')) { + if (first && !isIdentifierStartCharacter(c)) { return false; } - if (!first && !(isAlphanumeric(c) || c == '_' || c == '-')) { + if (!first && !isIdentifierCharacter(c)) { return false; } first = false; diff --git a/src/core/common/Utils.hpp b/src/core/common/Utils.hpp index 457d446..2c8a5b3 100644 --- a/src/core/common/Utils.hpp +++ b/src/core/common/Utils.hpp @@ -58,15 +58,23 @@ public: } /** - * Returns true if the given character is in [A-Za-z_] + * Returns true if the given character is in [A-Za-z]. */ - static bool isIdentifierStart(const char c) + static bool isIdentifierStartCharacter(const char c) { - return isAlphabetic(c) || (c == '_'); + return isAlphabetic(c); } /** - * Returns true if the given character is in [A-Za-z_][A-Za-z0-9_-]* + * Returns true if the given character is in [A-Za-z0-9_-]. + */ + static bool isIdentifierCharacter(const char c) + { + return isAlphanumeric(c) || (c == '_') || (c == '-'); + } + + /** + * Returns true if the given character is in [A-Za-z][A-Za-z0-9_-]* */ static bool isIdentifier(const std::string &name); diff --git a/src/core/common/VariantReader.hpp b/src/core/common/VariantReader.hpp index 7f00251..6b157d8 100644 --- a/src/core/common/VariantReader.hpp +++ b/src/core/common/VariantReader.hpp @@ -195,6 +195,7 @@ public: static std::pair<bool, Variant::mapType> parseObject(CharReader &reader, Logger &logger, char delim = 0); + /** * Parses a Cardinality. A Cardinality is specified as a list of Ranges, * separated by commas and enclosed in curly braces. The ranges can be diff --git a/src/plugins/plain/PlainFormatStreamReader.cpp b/src/plugins/plain/PlainFormatStreamReader.cpp index 498cd43..4469536 100644 --- a/src/plugins/plain/PlainFormatStreamReader.cpp +++ b/src/plugins/plain/PlainFormatStreamReader.cpp @@ -19,6 +19,7 @@ #include <core/common/CharReader.hpp> #include <core/common/Logger.hpp> #include <core/common/Utils.hpp> +#include <core/common/VariantReader.hpp> #include "PlainFormatStreamReader.hpp" @@ -47,7 +48,6 @@ private: SourceOffset end; public: - /** * Default constructor, initializes start and end with zeros. */ @@ -123,6 +123,72 @@ PlainFormatStreamReader::PlainFormatStreamReader(CharReader &reader, tokenBlockCommentEnd = tokenizer.registerToken("}%"); } +Variant PlainFormatStreamReader::parseIdentifier(size_t start) +{ + bool first = true; + std::vector<char> identifier; + size_t end = reader.getPeekOffset(); + char c; + while (reader.peek(c)) { + // Abort if this character is not a valid identifer character + if ((first && Utils::isIdentifierStartCharacter(c)) || + (!first && Utils::isIdentifierCharacter(c))) { + identifier.push_back(c); + } else { + reader.resetPeek(); + break; + } + + // This is no longer the first character + first = false; + end = reader.getPeekOffset(); + reader.consumePeek(); + } + + // Return the identifier at its location + Variant res = + Variant::fromString(std::string(identifier.data(), identifier.size())); + res.setLocation({reader.getSourceId(), start, end}); + return res; +} + +void PlainFormatStreamReader::parseCommand(size_t start) +{ + // Parse the commandName as a first identifier + commandName = parseIdentifier(start); + + // Check whether the next character is a '#', indicating the start of the + // command name + Variant commandArgName; + start = reader.getOffset(); + if (reader.expect('#')) { + commandArgName = parseIdentifier(start); + if (commandArgName.asString().empty()) { + logger.error("Expected identifier after '#'", commandArgName); + } + } + + // Read the arguments (if they are available), otherwise reset them + if (reader.expect('[')) { + auto res = VariantReader::parseObject(reader, logger, ']'); + commandArguments = res.second; + } else { + commandArguments = Variant::mapType{}; + } + + // Insert the parsed name, make sure "name" was not specified in the + // arguments + if (commandArgName.isString()) { + auto res = commandArguments.asMap().emplace("name", commandArgName); + if (!res.second) { + logger.error("Name argument specified multiple times", + SourceLocation{}, MessageMode::NO_CONTEXT); + logger.note("First occurance is here: ", commandArgName); + logger.note("Second occurance is here: ", res.first->second); + } + } +} + void PlainFormatStreamReader::parseBlockComment() { DynamicToken token; @@ -184,9 +250,10 @@ PlainFormatStreamReader::State PlainFormatStreamReader::parse() char c; reader.consumePeek(); reader.peek(c); - if (Utils::isIdentifierStart(c)) { + if (Utils::isIdentifierStartCharacter(c)) { CHECK_ISSUE_DATA(); - // TODO: Parse a command + reader.resetPeek(); + parseCommand(token.location.getStart()); return State::COMMAND; } diff --git a/src/plugins/plain/PlainFormatStreamReader.hpp b/src/plugins/plain/PlainFormatStreamReader.hpp index b2ea378..737bbe8 100644 --- a/src/plugins/plain/PlainFormatStreamReader.hpp +++ b/src/plugins/plain/PlainFormatStreamReader.hpp @@ -178,6 +178,22 @@ private: size_t fieldIdx; /** + * Function used internall to parse an identifier. + * + * @param start is the start byte offset of the identifier (including the + * backslash). + */ + Variant parseIdentifier(size_t start); + + /** + * Function used internally to parse a command. + * + * @param start is the start byte offset of the command (including the + * backslash). + */ + void parseCommand(size_t start); + + /** * Function used internally to parse a block comment. */ void parseBlockComment(); @@ -208,10 +224,32 @@ public: */ State parse(); - /** - * Returns a reference at the internally stored data. + /** + * Returns a reference at the internally stored data. Only valid if + * State::DATA was returned by the "parse" function. + * + * @return a reference at a variant containing the data parsed by the + * "parse" function. */ const Variant& getData() {return data;} + + /** + * Returns a reference at the internally stored command name. Only valid if + * State::COMMAND was returned by the "parse" function. + * + * @return a reference at a variant containing name and location of the + * parsed command. + */ + const Variant& getCommandName() {return commandName;} + + /** + * Returns a reference at the internally stored command name. Only valid if + * State::COMMAND was returned by the "parse" function. + * + * @return a reference at a variant containing arguments given to the + * command. + */ + const Variant& getCommandArguments() {return commandArguments;} }; } diff --git a/test/core/common/UtilsTest.cpp b/test/core/common/UtilsTest.cpp index c8f6922..917f45c 100644 --- a/test/core/common/UtilsTest.cpp +++ b/test/core/common/UtilsTest.cpp @@ -26,7 +26,7 @@ TEST(Utils, isIdentifier) { ASSERT_TRUE(Utils::isIdentifier("test")); ASSERT_TRUE(Utils::isIdentifier("t0-_est")); - ASSERT_TRUE(Utils::isIdentifier("_t0-_EST")); + ASSERT_FALSE(Utils::isIdentifier("_t0-_EST")); ASSERT_FALSE(Utils::isIdentifier("-t0-_EST")); ASSERT_FALSE(Utils::isIdentifier("0t-_EST")); ASSERT_FALSE(Utils::isIdentifier("invalid key")); diff --git a/test/plugins/plain/PlainFormatStreamReaderTest.cpp b/test/plugins/plain/PlainFormatStreamReaderTest.cpp index c44d575..38d7bb1 100644 --- a/test/plugins/plain/PlainFormatStreamReaderTest.cpp +++ b/test/plugins/plain/PlainFormatStreamReaderTest.cpp @@ -233,7 +233,136 @@ TEST(PlainFormatStreamReader, nestedMultilineComment) ASSERT_EQ(PlainFormatStreamReader::State::END, reader.parse()); } +TEST(PlainFormatStreamReader, simpleCommand) +{ + const char *testString = "\\test"; + // 0 12345 + CharReader charReader(testString); + PlainFormatStreamReader reader(charReader, logger); + ASSERT_EQ(PlainFormatStreamReader::State::COMMAND, reader.parse()); + + Variant commandName = reader.getCommandName(); + ASSERT_EQ("test", commandName.asString()); + + SourceLocation loc = commandName.getLocation(); + ASSERT_EQ(0U, loc.getStart()); + ASSERT_EQ(5U, loc.getEnd()); + + ASSERT_EQ(0U, reader.getCommandArguments().asMap().size()); + ASSERT_EQ(PlainFormatStreamReader::State::END, reader.parse()); +} + +TEST(PlainFormatStreamReader, simpleCommandWithName) +{ + const char *testString = "\\test#bla"; + // 0 12345678 + CharReader charReader(testString); + PlainFormatStreamReader reader(charReader, logger); + ASSERT_EQ(PlainFormatStreamReader::State::COMMAND, reader.parse()); + + Variant commandName = reader.getCommandName(); + ASSERT_EQ("test", commandName.asString()); + SourceLocation loc = commandName.getLocation(); + ASSERT_EQ(0U, loc.getStart()); + ASSERT_EQ(5U, loc.getEnd()); + + Variant commandArguments = reader.getCommandArguments(); + ASSERT_TRUE(commandArguments.isMap()); + ASSERT_EQ(1U, commandArguments.asMap().size()); + ASSERT_EQ(1U, commandArguments.asMap().count("name")); + ASSERT_EQ("bla", commandArguments.asMap()["name"].asString()); + + loc = commandArguments.asMap()["name"].getLocation(); + ASSERT_EQ(5U, loc.getStart()); + ASSERT_EQ(9U, loc.getEnd()); + + ASSERT_EQ(PlainFormatStreamReader::State::END, reader.parse()); +} + +TEST(PlainFormatStreamReader, simpleCommandWithArguments) +{ + const char *testString = "\\test[a=1,b=2,c=\"test\"]"; + // 0 123456789012345 678901 2 + // 0 1 2 + CharReader charReader(testString); + PlainFormatStreamReader reader(charReader, logger); + ASSERT_EQ(PlainFormatStreamReader::State::COMMAND, reader.parse()); + + Variant commandName = reader.getCommandName(); + ASSERT_EQ("test", commandName.asString()); + SourceLocation loc = commandName.getLocation(); + ASSERT_EQ(0U, loc.getStart()); + ASSERT_EQ(5U, loc.getEnd()); + + Variant commandArguments = reader.getCommandArguments(); + ASSERT_TRUE(commandArguments.isMap()); + ASSERT_EQ(3U, commandArguments.asMap().size()); + ASSERT_EQ(1U, commandArguments.asMap().count("a")); + ASSERT_EQ(1U, commandArguments.asMap().count("b")); + ASSERT_EQ(1U, commandArguments.asMap().count("c")); + ASSERT_EQ(1, commandArguments.asMap()["a"].asInt()); + ASSERT_EQ(2, commandArguments.asMap()["b"].asInt()); + ASSERT_EQ("test", commandArguments.asMap()["c"].asString()); + + loc = commandArguments.asMap()["a"].getLocation(); + ASSERT_EQ(8U, loc.getStart()); + ASSERT_EQ(9U, loc.getEnd()); + + loc = commandArguments.asMap()["b"].getLocation(); + ASSERT_EQ(12U, loc.getStart()); + ASSERT_EQ(13U, loc.getEnd()); + + loc = commandArguments.asMap()["c"].getLocation(); + ASSERT_EQ(16U, loc.getStart()); + ASSERT_EQ(22U, loc.getEnd()); + + ASSERT_EQ(PlainFormatStreamReader::State::END, reader.parse()); +} +TEST(PlainFormatStreamReader, simpleCommandWithArgumentsAndName) +{ + const char *testString = "\\test#bla[a=1,b=2,c=\"test\"]"; + // 0 1234567890123456789 01234 56 + // 0 1 2 + CharReader charReader(testString); + PlainFormatStreamReader reader(charReader, logger); + ASSERT_EQ(PlainFormatStreamReader::State::COMMAND, reader.parse()); + Variant commandName = reader.getCommandName(); + ASSERT_EQ("test", commandName.asString()); + SourceLocation loc = commandName.getLocation(); + ASSERT_EQ(0U, loc.getStart()); + ASSERT_EQ(5U, loc.getEnd()); + + Variant commandArguments = reader.getCommandArguments(); + ASSERT_TRUE(commandArguments.isMap()); + ASSERT_EQ(4U, commandArguments.asMap().size()); + ASSERT_EQ(1U, commandArguments.asMap().count("a")); + ASSERT_EQ(1U, commandArguments.asMap().count("b")); + ASSERT_EQ(1U, commandArguments.asMap().count("c")); + ASSERT_EQ(1U, commandArguments.asMap().count("name")); + ASSERT_EQ(1, commandArguments.asMap()["a"].asInt()); + ASSERT_EQ(2, commandArguments.asMap()["b"].asInt()); + ASSERT_EQ("test", commandArguments.asMap()["c"].asString()); + ASSERT_EQ("bla", commandArguments.asMap()["name"].asString()); + + loc = commandArguments.asMap()["a"].getLocation(); + ASSERT_EQ(12U, loc.getStart()); + ASSERT_EQ(13U, loc.getEnd()); + + loc = commandArguments.asMap()["b"].getLocation(); + ASSERT_EQ(16U, loc.getStart()); + ASSERT_EQ(17U, loc.getEnd()); + + loc = commandArguments.asMap()["c"].getLocation(); + ASSERT_EQ(20U, loc.getStart()); + ASSERT_EQ(26U, loc.getEnd()); + + loc = commandArguments.asMap()["name"].getLocation(); + ASSERT_EQ(5U, loc.getStart()); + ASSERT_EQ(9U, loc.getEnd()); + + ASSERT_EQ(PlainFormatStreamReader::State::END, reader.parse()); +} } |