summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Paassen <bpaassen@techfak.uni-bielefeld.de>2015-02-08 21:42:58 +0100
committerBenjamin Paassen <bpaassen@techfak.uni-bielefeld.de>2015-02-08 21:42:58 +0100
commitfbaad57be8ddf3f90eb13551cc7eb18674b3efa2 (patch)
tree0c7390346731ebfd739f273b7f7d624608fa3be5
parent616e9bfeee29556952ebdccf57cb1fd304744acb (diff)
parentbb8c69fbdeba3fa95fc780552252525b222ceb5a (diff)
Merge branch 'master' of somweyr.de:ousia
-rw-r--r--src/core/common/CharReader.cpp12
-rw-r--r--src/core/common/CharReader.hpp9
-rw-r--r--src/core/common/Utils.cpp4
-rw-r--r--src/core/common/Utils.hpp16
-rw-r--r--src/core/common/VariantReader.hpp1
-rw-r--r--src/plugins/plain/PlainFormatStreamReader.cpp73
-rw-r--r--src/plugins/plain/PlainFormatStreamReader.hpp42
-rw-r--r--test/core/common/UtilsTest.cpp2
-rw-r--r--test/plugins/plain/PlainFormatStreamReaderTest.cpp129
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());
+}
}