summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Stöckel <astoecke@techfak.uni-bielefeld.de>2015-02-15 00:14:58 +0100
committerAndreas Stöckel <astoecke@techfak.uni-bielefeld.de>2015-02-15 00:14:58 +0100
commit0a8a012850bb7c730ccac4c91c7aca5c88cbedc9 (patch)
tree88bc153ca503e8efcf491c66a621b71ffe314db0
parent22c9d5b5504c81902ccbfae386cf69351d7d0209 (diff)
Implemented most of the desired behaviour of the Stack class, added unit tests
-rw-r--r--src/core/parser/stack/Stack.cpp437
-rw-r--r--src/core/parser/stack/Stack.hpp85
-rw-r--r--test/core/parser/stack/StackTest.cpp639
3 files changed, 1075 insertions, 86 deletions
diff --git a/src/core/parser/stack/Stack.cpp b/src/core/parser/stack/Stack.cpp
index b0df39b..d84a19c 100644
--- a/src/core/parser/stack/Stack.cpp
+++ b/src/core/parser/stack/Stack.cpp
@@ -18,6 +18,7 @@
#include <sstream>
+#include <core/common/Logger.hpp>
#include <core/common/Utils.hpp>
#include <core/common/Exceptions.hpp>
#include <core/parser/ParserScope.hpp>
@@ -37,10 +38,28 @@ HandlerInfo::HandlerInfo() : HandlerInfo(nullptr) {}
HandlerInfo::HandlerInfo(std::shared_ptr<Handler> handler)
: handler(handler),
fieldIdx(0),
+ valid(true),
+ implicit(false),
inField(false),
inDefaultField(false),
inImplicitDefaultField(false),
- hasDefaultField(false)
+ inValidField(false),
+ hadDefaultField(false)
+{
+}
+
+HandlerInfo::HandlerInfo(bool valid, bool implicit, bool inField,
+ bool inDefaultField, bool inImplicitDefaultField,
+ bool inValidField)
+ : handler(nullptr),
+ fieldIdx(0),
+ valid(valid),
+ implicit(implicit),
+ inField(inField),
+ inDefaultField(inDefaultField),
+ inImplicitDefaultField(inImplicitDefaultField),
+ inValidField(inValidField),
+ hadDefaultField(false)
{
}
@@ -55,7 +74,7 @@ void HandlerInfo::fieldStart(bool isDefault, bool isImplicit, bool isValid)
inDefaultField = isDefault || isImplicit;
inImplicitDefaultField = isImplicit;
inValidField = isValid;
- hasDefaultField = hasDefaultField || inDefaultField;
+ hadDefaultField = hadDefaultField || inDefaultField;
fieldIdx++;
}
@@ -65,11 +84,13 @@ void HandlerInfo::fieldEnd()
inDefaultField = false;
inImplicitDefaultField = false;
inValidField = false;
- if (fieldIdx > 0) {
- fieldIdx--;
- }
}
+/**
+ * Stub instance of HandlerInfo containing no handler information.
+ */
+static HandlerInfo EmptyHandlerInfo{true, true, true, true, false, true};
+
/* Helper functions */
/**
@@ -110,9 +131,31 @@ Stack::Stack(ParserContext &ctx,
}
}
-Stack::~Stack() {}
+Stack::~Stack()
+{
+ while (!stack.empty()) {
+ // Fetch the topmost stack element
+ HandlerInfo &info = currentInfo();
+
+ // It is an error if we're still in a field of an element while the
+ // Stack instance is destroyed. Log that
+ if (handlersValid()) {
+ if (info.inField && !info.implicit &&
+ !info.inImplicitDefaultField) {
+ logger().error(
+ std::string("Reached end of stream, but command \"") +
+ info.handler->getName() +
+ "\" has not ended yet. Command was started here:",
+ info.handler->getLocation());
+ }
+ }
-bool Stack::deduceState()
+ // Remove the command from the stack
+ endCurrentHandler();
+ }
+}
+
+void Stack::deduceState()
{
// Assemble all states
std::vector<const State *> states;
@@ -125,23 +168,24 @@ bool Stack::deduceState()
std::vector<const State *> possibleStates =
StateDeductor(ctx.getScope().getStackTypeSignature(), states).deduce();
if (possibleStates.size() != 1U) {
- throw LoggableException{
- "Error while including file: Cannot deduce parser state."};
+ throw LoggableException(
+ "Error while including file: Cannot deduce parser state.");
}
- // Switch to this state by creating a dummy handler
- const State *state = possibleStates[0];
- stack.emplace(std::shared_ptr<Handler>{EmptyHandler::create({ctx, "", *state, *state, SourceLocation{}})});
-}
+ // Switch to this state by creating a handler, but do not call its start
+ // function
+ const State &state = *possibleStates[0];
+ HandlerConstructor ctor =
+ state.elementHandler ? state.elementHandler : EmptyHandler::create;
-bool Stack::handlersValid()
-{
- for (auto it = stack.crbegin(); it != stack.crend(); it++) {
- if (!it->valid) {
- return false;
- }
- }
- return true;
+ std::shared_ptr<Handler> handler =
+ std::shared_ptr<Handler>{ctor({ctx, "", state, SourceLocation{}})};
+ stack.emplace_back(handler);
+
+ // Set the correct flags for this implicit handler
+ HandlerInfo &info = currentInfo();
+ info.implicit = true;
+ info.fieldStart(true, false, true);
}
std::set<std::string> Stack::expectedCommands()
@@ -158,12 +202,12 @@ std::set<std::string> Stack::expectedCommands()
const State &Stack::currentState()
{
- return stack.empty() ? States::None : stack.top()->state();
+ return stack.empty() ? States::None : stack.back().handler->getState();
}
std::string Stack::currentCommandName()
{
- return stack.empty() ? std::string{} : stack.top()->name();
+ return stack.empty() ? std::string{} : stack.back().handler->getName();
}
const State *Stack::findTargetState(const std::string &name)
@@ -180,77 +224,330 @@ const State *Stack::findTargetState(const std::string &name)
return nullptr;
}
+const State *Stack::findTargetStateOrWildcard(const std::string &name)
+{
+ // Try to find the target state with the given name, if none is found, try
+ // find a matching "*" state.
+ State const *targetState = findTargetState(name);
+ if (targetState == nullptr) {
+ return findTargetState("*");
+ }
+ return targetState;
+}
+
+HandlerInfo &Stack::currentInfo()
+{
+ return stack.empty() ? EmptyHandlerInfo : stack.back();
+}
+HandlerInfo &Stack::lastInfo()
+{
+ return stack.size() < 2U ? EmptyHandlerInfo : stack[stack.size() - 2];
+}
+
+void Stack::endCurrentHandler()
+{
+ if (!stack.empty()) {
+ // Fetch the handler info for the current top-level element
+ HandlerInfo &info = stack.back();
+
+ // Do not call any callback functions while the stack is marked as
+ // invalid or this is an elment marked as "implicit"
+ if (!info.implicit && handlersValid()) {
+ // Make sure the fieldEnd handler is called if the element still
+ // is in a field
+ if (info.inField) {
+ info.handler->fieldEnd();
+ info.fieldEnd();
+ }
+
+ // Call the "end" function of the corresponding Handler instance
+ info.handler->end();
+ }
+
+ // Remove the element from the stack
+ stack.pop_back();
+ }
+}
+
+bool Stack::ensureHandlerIsInField()
+{
+ // If the current handler is not in a field (and actually has a handler)
+ // try to start a default field
+ HandlerInfo &info = currentInfo();
+ if (!info.inField && info.handler != nullptr) {
+ // Abort if the element already had a default field
+ if (info.hadDefaultField) {
+ return false;
+ }
+
+ // Try to start a new default field, abort if this did not work
+ bool isDefault = true;
+ if (!info.handler->fieldStart(isDefault, info.fieldIdx)) {
+ info.handler->fieldEnd();
+ endCurrentHandler();
+ return false;
+ }
+
+ // Mark the field as started
+ info.fieldStart(true, true, true);
+ }
+ return true;
+}
+
+bool Stack::handlersValid()
+{
+ for (auto it = stack.crbegin(); it != stack.crend(); it++) {
+ if (!it->valid) {
+ return false;
+ }
+ }
+ return true;
+}
+
+Logger &Stack::logger() { return ctx.getLogger(); }
+
void Stack::command(const Variant &name, const Variant::mapType &args)
{
- // Make sure the given identifier is valid
+ // Make sure the given identifier is valid (preventing "*" from being
+ // malicously passed to this function)
if (!Utils::isNamespacedIdentifier(name.asString())) {
throw LoggableException(std::string("Invalid identifier \"") +
- name.asString() + std::string("\""), name);
+ name.asString() + std::string("\""),
+ name);
}
- // Try to find a target state for the given command
- State const *targetState = findTargetState(name.asString());
+ State const *lastTargetState = nullptr;
+ Variant::mapType canonicalArgs;
+ while (true) {
+ // Try to find a target state for the given command, if none can be
+ // found and the current command does not have an open field, then try
+ // to create an empty default field, otherwise this is an exception
+ const State *targetState = findTargetStateOrWildcard(name.asString());
+ if (targetState == nullptr) {
+ if (!currentInfo().inField) {
+ endCurrentHandler();
+ continue;
+ } else {
+ throw buildInvalidCommandException(name.asString(),
+ expectedCommands());
+ }
+ }
+
+ // Make sure we're currently inside a field
+ if (!ensureHandlerIsInField()) {
+ endCurrentHandler();
+ continue;
+ }
- // No target state is found, try to find a wildcard handler for the current
- // state
- if (targetState == nullptr) {
- targetState = findTargetState("*");
- }
+ // Fork the logger. We do not want any validation errors to skip
+ LoggerFork loggerFork = logger().fork();
- // No handler has been found at all,
- if (targetState == nullptr) {
- throw buildInvalidCommandException(name.asString(), expectedCommands());
+ // Canonicalize the arguments (if this has not already been done), allow
+ // additional arguments
+ if (lastTargetState != targetState) {
+ canonicalArgs = args;
+ targetState->arguments.validateMap(canonicalArgs, loggerFork, true);
+ lastTargetState = targetState;
+ }
+
+ // Instantiate the handler and push it onto the stack
+ HandlerConstructor ctor = targetState->elementHandler
+ ? targetState->elementHandler
+ : EmptyHandler::create;
+ std::shared_ptr<Handler> handler{
+ ctor({ctx, name.asString(), *targetState, name.getLocation()})};
+ stack.emplace_back(handler);
+
+ // Fetch the HandlerInfo for the parent element and the current element
+ HandlerInfo &parentInfo = lastInfo();
+ HandlerInfo &info = currentInfo();
+
+ // Call the "start" method of the handler, store the result of the start
+ // method as the validity of the handler -- do not call the start method
+ // if the stack is currently invalid (as this may cause further,
+ // unwanted errors)
+ bool validStack = handlersValid();
+ info.valid = false;
+ if (validStack) {
+ handler->setLogger(loggerFork);
+ try {
+ info.valid = handler->start(canonicalArgs);
+ }
+ catch (LoggableException ex) {
+ loggerFork.log(ex);
+ }
+ handler->resetLogger();
+ }
+
+ // We started the command within an implicit default field and it is not
+ // valid -- remove both the new handler and the parent field from the
+ // stack
+ if (!info.valid && parentInfo.inImplicitDefaultField) {
+ endCurrentHandler();
+ endCurrentHandler();
+ continue;
+ }
+
+ // If we ended up here, starting the command may or may not have worked,
+ // but after all, we cannot unroll the stack any further. Update the
+ // "valid" flag, commit any potential error messages and return.
+ info.valid = parentInfo.valid && info.valid;
+ loggerFork.commit();
+ return;
}
+}
+
+void Stack::data(const Variant &data)
+{
+ while (true) {
+ // Check whether there is any command the data can be sent to
+ if (stack.empty()) {
+ throw LoggableException("No command here to receive data.");
+ }
+
+ // Fetch the current command handler information
+ HandlerInfo &info = currentInfo();
+
+ // Make sure the current handler has an open field
+ if (!ensureHandlerIsInField()) {
+ endCurrentHandler();
+ continue;
+ }
+
+ // If this field should not get any data, log an error and do not call
+ // the "data" handler
+ if (!info.inValidField) {
+ logger().error("Did not expect any data here", data);
+ }
+
+ if (handlersValid() && info.inValidField) {
+ // Fork the logger and set it as temporary logger for the "start"
+ // method. We only want to keep error messages if this was not a try
+ // to implicitly open a default field.
+ LoggerFork loggerFork = logger().fork();
+ info.handler->setLogger(loggerFork);
+
+ // Pass the data to the current Handler instance
+ bool valid = false;
+ try {
+ valid = info.handler->data(data);
+ }
+ catch (LoggableException ex) {
+ loggerFork.log(ex);
+ }
+
+ // Reset the logger instance as soon as possible
+ info.handler->resetLogger();
+
+ // If placing the data here failed and we're currently in an
+ // implicitly opened field, just unroll the stack to the next field
+ // and try again
+ if (!valid && info.inImplicitDefaultField) {
+ endCurrentHandler();
+ continue;
+ }
+
+ // Commit the content of the logger fork. Do not change the valid
+ // flag.
+ loggerFork.commit();
+ }
- // Fetch the associated constructor
- HandlerConstructor ctor = targetState->elementHandler
- ? targetState->elementHandler
- : DefaultHandler::create;
-
- // Canonicalize the arguments, allow additional arguments
- targetState->arguments.validateMap(args, ctx.getLogger(), true);
-
- // Instantiate the handler and push it onto the stack
- Handler *handler =
- ctor({ctx, name.asString(), *targetState, currentState(), name.getLocation()});
- stack.emplace_back(std::shared_ptr<Handler>{handler});
-
- // Call the "start" method of the handler, store the result of the start
- // method as the validity of the handler -- do not call the start method
- // if the stack is currently invalid (as this may cause further, unwanted
- // errors)
- try {
- stack.back().valid = handlersValid() && handler->start(args);
- } catch (LoggableException ex) {
- stack.back().valid = false;
- logger.log(ex, )
+ // There was no reason to unroll the stack any further, so continue
+ return;
}
}
-void Stack::end()
+void Stack::fieldStart(bool isDefault)
{
- // Check whether the current command could be ended
+ // Make sure the current handler stack is not empty
if (stack.empty()) {
- throw LoggableException{"No command to end."};
+ throw LoggableException(
+ "No command for which a field could be started");
}
- // Remove the current HandlerInstance from the stack
- std::shared_ptr<Handler> inst{stack.top()};
- stack.pop();
+ // Fetch the information attached to the current handler
+ HandlerInfo &info = currentInfo();
+ if (info.inField) {
+ logger().error(
+ "Got field start, but there is no command for which to start the "
+ "field.");
+ return;
+ }
+
+ // Copy the isDefault flag to a local variable, the fieldStart method will
+ // write into this variable
+ bool defaultField = isDefault;
+
+ // Do not call the "fieldStart" function if we're in an invalid subtree
+ bool valid = false;
+ if (handlersValid()) {
+ try {
+ valid = info.handler->fieldStart(defaultField, info.fieldIdx);
+ }
+ catch (LoggableException ex) {
+ logger().log(ex);
+ }
+ if (!valid && !defaultField) {
+ logger().error(
+ std::string("Cannot start a new field here (index ") +
+ std::to_string(info.fieldIdx + 1) +
+ std::string("), field does not exist"));
+ }
+ }
- // Call the end function of the last Handler
- inst->end();
+ // Mark the field as started
+ info.fieldStart(defaultField, false, valid);
}
-void Stack::data(const std::string &data, int field)
+void Stack::fieldEnd()
{
- // Check whether there is any command the data can be sent to
+ // Make sure the current handler stack is not empty
if (stack.empty()) {
- throw LoggableException{"No command to receive data."};
+ throw LoggableException("No command for which a field could be ended");
}
- // Pass the data to the current Handler instance
- stack.top()->data(data, field);
+ // Fetch the information attached to the current handler
+ HandlerInfo &info = currentInfo();
+ if (!info.inField) {
+ logger().error(
+ "Got field end, but there is no command for which to end the "
+ "field.");
+ return;
+ }
+
+ // Only continue if the current handler stack is in a valid state, do not
+ // call the fieldEnd function if something went wrong before
+ if (handlersValid()) {
+ try {
+ info.handler->fieldEnd();
+ }
+ catch (LoggableException ex) {
+ logger().log(ex);
+ }
+ }
+
+ // This command no longer is in a field
+ info.fieldEnd();
+
+ // As soon as this command had a default field, remove it from the stack
+ if (info.hadDefaultField) {
+ endCurrentHandler();
+ }
+}
+
+void Stack::annotationStart(const Variant &className, const Variant &args)
+{
+ // TODO
+}
+
+void Stack::annotationEnd(const Variant &className, const Variant &elementName)
+{
+ // TODO
+}
+
+void Stack::token(Variant token)
+{
+ // TODO
}
}
}
diff --git a/src/core/parser/stack/Stack.hpp b/src/core/parser/stack/Stack.hpp
index 294f7ec..76eefd9 100644
--- a/src/core/parser/stack/Stack.hpp
+++ b/src/core/parser/stack/Stack.hpp
@@ -43,6 +43,7 @@ namespace ousia {
// Forward declarations
class ParserContext;
+class Logger;
namespace parser_stack {
@@ -75,7 +76,13 @@ public:
bool valid : 1;
/**
- * Set to true if the handler currently is in a filed.
+ * Set to true if this is an implicit handler, that was created when the
+ * current stack state was deduced.
+ */
+ bool implicit : 1;
+
+ /**
+ * Set to true if the handler currently is in a field.
*/
bool inField : 1;
@@ -99,12 +106,17 @@ public:
/**
* Set to true, if the default field was already started.
*/
- bool hasDefaultField : 1;
+ bool hadDefaultField : 1;
/**
* Default constructor of the HandlerInfo class.
*/
HandlerInfo();
+ /**
+ * Constructor of the HandlerInfo class, allows to set all flags manually.
+ */
+ HandlerInfo(bool valid, bool implicit, bool inField, bool inDefaultField,
+ bool inImplicitDefaultField, bool inValidField);
/**
* Constructor of the HandlerInfo class, taking a shared_ptr to the handler
@@ -129,7 +141,6 @@ public:
void fieldEnd();
};
-
/**
* The Stack class is a pushdown automaton responsible for turning a command
* stream into a tree of Node instances. It does so by following a state
@@ -155,6 +166,11 @@ private:
std::vector<HandlerInfo> stack;
/**
+ * Return the reference in the Logger instance stored within the context.
+ */
+ Logger &logger();
+
+ /**
* Used internally to get all expected command names for the current state.
* This function is used to build error messages.
*
@@ -164,7 +180,7 @@ private:
/**
* Returns the targetState for a command with the given name that can be
- * reached from for the current state.
+ * reached from the current state.
*
* @param name is the name of the requested command.
* @return nullptr if no target state was found, a pointer at the target
@@ -173,6 +189,17 @@ private:
const State *findTargetState(const std::string &name);
/**
+ * Returns the targetState for a command with the given name that can be
+ * reached from the current state, also including the wildcard "*" state.
+ * Throws an exception if the given target state is not a valid identifier.
+ *
+ * @param name is the name of the requested command.
+ * @return nullptr if no target state was found, a pointer at the target
+ * state otherwise.
+ */
+ const State *findTargetStateOrWildcard(const std::string &name);
+
+ /**
* Tries to reconstruct the parser state from the Scope instance of the
* ParserContext given in the constructor. This functionality is needed for
* including files,as the Parser of the included file needs to be brought to
@@ -181,6 +208,33 @@ private:
void deduceState();
/**
+ * Returns a reference at the current HandlerInfo instance (or a stub
+ * HandlerInfo instance if the stack is empty).
+ */
+ HandlerInfo &currentInfo();
+
+ /**
+ * Returns a reference at the last HandlerInfo instance (or a stub
+ * HandlerInfo instance if the stack has only one element).
+ */
+ HandlerInfo &lastInfo();
+
+ /**
+ * Ends the current handler and removes the corresponding element from the
+ * stack.
+ */
+ void endCurrentHandler();
+
+ /**
+ * Tries to start a default field for the current handler, if currently the
+ * handler is not inside a field and did not have a default field yet.
+ *
+ * @return true if the handler is inside a field, false if no field could
+ * be started.
+ */
+ bool ensureHandlerIsInField();
+
+ /**
* Returns true if all handlers on the stack are currently valid, or false
* if at least one handler is invalid.
*
@@ -196,9 +250,8 @@ public:
* @param states is a map containing the command names and pointers at the
* corresponding State instances.
*/
- Stack(
- ParserContext &ctx,
- const std::multimap<std::string, const State *> &states);
+ Stack(ParserContext &ctx,
+ const std::multimap<std::string, const State *> &states);
/**
* Destructor of the Stack class.
@@ -232,6 +285,15 @@ public:
void command(const Variant &name, const Variant::mapType &args);
/**
+ * Function that shuold be called whenever character data is found in the
+ * input stream. May only be called if the currently is a command on the
+ * stack.
+ *
+ * @param data is a string variant containing the data that has been found.
+ */
+ void data(const Variant &data);
+
+ /**
* Function that should be called whenever a new field starts. Fields of the
* same command may not be separated by calls to data or annotations. Doing
* so will result in a LoggableException.
@@ -248,15 +310,6 @@ public:
void fieldEnd();
/**
- * Function that shuold be called whenever character data is found in the
- * input stream. May only be called if the currently is a command on the
- * stack.
- *
- * @param data is a string variant containing the data that has been found.
- */
- void data(const Variant &data);
-
- /**
* Function that should be called whenever an annotation starts.
*
* @param name is the name of the annotation class.
diff --git a/test/core/parser/stack/StackTest.cpp b/test/core/parser/stack/StackTest.cpp
new file mode 100644
index 0000000..7cc8bc5
--- /dev/null
+++ b/test/core/parser/stack/StackTest.cpp
@@ -0,0 +1,639 @@
+/*
+ Ousía
+ Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include <core/frontend/TerminalLogger.hpp>
+#include <core/parser/stack/Handler.hpp>
+#include <core/parser/stack/Stack.hpp>
+#include <core/parser/stack/State.hpp>
+
+#include <core/StandaloneEnvironment.hpp>
+
+namespace ousia {
+namespace parser_stack {
+
+// Build an instance of the StandaloneEnvironment used for this unit test
+static TerminalLogger logger(std::cerr, true);
+// static ConcreteLogger logger;
+static StandaloneEnvironment env(logger);
+
+namespace {
+
+struct Tracker {
+ int startCount;
+ int endCount;
+ int fieldStartCount;
+ int fieldEndCount;
+ int annotationStartCount;
+ int annotationEndCount;
+ int dataCount;
+
+ Variant::mapType startArgs;
+ bool fieldStartIsDefault;
+ size_t fieldStartIdx;
+ Variant annotationStartClassName;
+ Variant::mapType annotationStartArgs;
+ Variant annotationEndClassName;
+ Variant annotationEndElementName;
+ Variant dataData;
+
+ bool startResult;
+ bool fieldStartSetIsDefault;
+ bool fieldStartResult;
+ bool annotationStartResult;
+ bool annotationEndResult;
+ bool dataResult;
+
+ Tracker() { reset(); }
+
+ void reset()
+ {
+ startCount = 0;
+ endCount = 0;
+ fieldStartCount = 0;
+ fieldEndCount = 0;
+ annotationStartCount = 0;
+ annotationEndCount = 0;
+ dataCount = 0;
+
+ startArgs = Variant::mapType{};
+ fieldStartIsDefault = false;
+ fieldStartIdx = 0;
+ annotationStartClassName = Variant::fromString(std::string{});
+ annotationStartArgs = Variant::mapType{};
+ annotationEndClassName = Variant::fromString(std::string{});
+ annotationEndElementName = Variant::fromString(std::string{});
+ dataData = Variant::fromString(std::string{});
+
+ startResult = true;
+ fieldStartSetIsDefault = false;
+ fieldStartResult = true;
+ annotationStartResult = true;
+ annotationEndResult = true;
+ dataResult = true;
+ }
+
+ void expect(int startCount, int endCount, int fieldStartCount,
+ int fieldEndCount, int annotationStartCount,
+ int annotationEndCount, int dataCount)
+ {
+ EXPECT_EQ(startCount, this->startCount);
+ EXPECT_EQ(endCount, this->endCount);
+ EXPECT_EQ(fieldStartCount, this->fieldStartCount);
+ EXPECT_EQ(fieldEndCount, this->fieldEndCount);
+ EXPECT_EQ(annotationStartCount, this->annotationStartCount);
+ EXPECT_EQ(annotationEndCount, this->annotationEndCount);
+ EXPECT_EQ(dataCount, this->dataCount);
+ }
+};
+
+static Tracker tracker;
+
+class TestHandler : public Handler {
+private:
+ TestHandler(const HandlerData &handlerData) : Handler(handlerData) {}
+
+public:
+ bool start(const Variant::mapType &args)
+ {
+ tracker.startCount++;
+ tracker.startArgs = args;
+ return tracker.startResult;
+ }
+
+ void end() { tracker.endCount++; }
+
+ bool fieldStart(bool &isDefault, size_t fieldIdx)
+ {
+ tracker.fieldStartCount++;
+ tracker.fieldStartIsDefault = isDefault;
+ tracker.fieldStartIdx = fieldIdx;
+ if (tracker.fieldStartSetIsDefault) {
+ isDefault = true;
+ }
+ return tracker.fieldStartResult;
+ }
+
+ void fieldEnd() { tracker.fieldEndCount++; }
+
+ bool annotationStart(const Variant &className, const Variant::mapType &args)
+ {
+ tracker.annotationStartCount++;
+ tracker.annotationStartClassName = className;
+ tracker.annotationStartArgs = args;
+ return tracker.annotationStartResult;
+ }
+
+ bool annotationEnd(const Variant &className, const Variant &elementName)
+ {
+ tracker.annotationEndCount++;
+ tracker.annotationEndClassName = className;
+ tracker.annotationEndElementName = elementName;
+ return tracker.annotationEndResult;
+ }
+
+ bool data(const Variant &data)
+ {
+ tracker.dataCount++;
+ tracker.dataData = data;
+ return tracker.dataResult;
+ }
+
+ static Handler *create(const HandlerData &handlerData)
+ {
+ return new TestHandler(handlerData);
+ }
+};
+}
+
+namespace States {
+static const State Document =
+ StateBuilder().parent(&None).elementHandler(TestHandler::create);
+static const State Body =
+ StateBuilder().parent(&Document).elementHandler(TestHandler::create);
+static const State Empty =
+ StateBuilder().parent(&Document).elementHandler(TestHandler::create);
+static const State Special =
+ StateBuilder().parent(&All).elementHandler(TestHandler::create);
+static const State Arguments =
+ StateBuilder().parent(&None).elementHandler(TestHandler::create).arguments(
+ {Argument::Int("a"), Argument::String("b")});
+static const State BodyChildren =
+ StateBuilder().parent(&Body).elementHandler(TestHandler::create);
+static const State Any =
+ StateBuilder().parents({&None, &Any}).elementHandler(TestHandler::create);
+
+static const std::multimap<std::string, const State *> TestHandlers{
+ {"document", &Document},
+ {"body", &Body},
+ {"empty", &Empty},
+ {"special", &Special},
+ {"arguments", &Arguments},
+ {"*", &BodyChildren}};
+
+static const std::multimap<std::string, const State *> AnyHandlers{{"*", &Any}};
+}
+
+TEST(Stack, basicTest)
+{
+ tracker.reset();
+ logger.reset();
+ {
+ Stack s{env.context, States::TestHandlers};
+
+ EXPECT_EQ("", s.currentCommandName());
+ EXPECT_EQ(&States::None, &s.currentState());
+
+ s.command("document", {});
+ s.fieldStart(true);
+ s.data("test1");
+
+ EXPECT_EQ("document", s.currentCommandName());
+ EXPECT_EQ(&States::Document, &s.currentState());
+ tracker.expect(1, 0, 1, 0, 0, 0, 1); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.command("body", {});
+ s.fieldStart(true);
+ s.data("test2");
+ EXPECT_EQ("body", s.currentCommandName());
+ EXPECT_EQ(&States::Body, &s.currentState());
+ tracker.expect(2, 0, 2, 0, 0, 0, 2); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.command("inner", {});
+ s.fieldStart(true);
+ EXPECT_EQ("inner", s.currentCommandName());
+ EXPECT_EQ(&States::BodyChildren, &s.currentState());
+
+ s.fieldEnd();
+ tracker.expect(3, 1, 3, 1, 0, 0, 2); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.fieldEnd();
+ EXPECT_EQ("document", s.currentCommandName());
+ EXPECT_EQ(&States::Document, &s.currentState());
+ tracker.expect(3, 2, 3, 2, 0, 0, 2); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.command("body", {});
+ s.fieldStart(true);
+ s.data("test3");
+ EXPECT_EQ("body", s.currentCommandName());
+ EXPECT_EQ(&States::Body, &s.currentState());
+ s.fieldEnd();
+ tracker.expect(4, 3, 4, 3, 0, 0, 3); // sc, ec, fsc, fse, asc, aec, dc
+
+ EXPECT_EQ("document", s.currentCommandName());
+ EXPECT_EQ(&States::Document, &s.currentState());
+
+ s.fieldEnd();
+ tracker.expect(4, 4, 4, 4, 0, 0, 3); // sc, ec, fsc, fse, asc, aec, dc
+
+ EXPECT_EQ("", s.currentCommandName());
+ EXPECT_EQ(&States::None, &s.currentState());
+ }
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, errorInvalidCommands)
+{
+ Stack s{env.context, States::TestHandlers};
+ tracker.reset();
+ EXPECT_THROW(s.command("body", {}), LoggableException);
+ s.command("document", {});
+ s.fieldStart(true);
+ EXPECT_THROW(s.command("document", {}), LoggableException);
+ s.command("empty", {});
+ s.fieldStart(true);
+ EXPECT_THROW(s.command("body", {}), LoggableException);
+ s.command("special", {});
+ s.fieldStart(true);
+ s.fieldEnd();
+ s.fieldEnd();
+ s.fieldEnd();
+ EXPECT_EQ(&States::None, &s.currentState());
+ ASSERT_THROW(s.fieldEnd(), LoggableException);
+ ASSERT_THROW(s.data("test"), LoggableException);
+}
+
+TEST(Stack, validation)
+{
+ Stack s{env.context, States::TestHandlers};
+ tracker.reset();
+ logger.reset();
+
+ s.command("arguments", {});
+ EXPECT_TRUE(logger.hasError());
+ s.fieldStart(true);
+ s.fieldEnd();
+
+ logger.reset();
+ s.command("arguments", {{"a", 5}});
+ EXPECT_TRUE(logger.hasError());
+ s.fieldStart(true);
+ s.fieldEnd();
+
+ logger.reset();
+ s.command("arguments", {{"a", 5}, {"b", "test"}});
+ EXPECT_FALSE(logger.hasError());
+ s.fieldStart(true);
+ s.fieldEnd();
+}
+
+TEST(Stack, invalidCommandName)
+{
+ Stack s{env.context, States::AnyHandlers};
+ tracker.reset();
+ logger.reset();
+
+ s.command("a", {});
+ s.fieldStart(true);
+ s.fieldEnd();
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.command("a_", {});
+ s.fieldStart(true);
+ s.fieldEnd();
+ tracker.expect(2, 2, 2, 2, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.command("a_:b", {});
+ s.fieldStart(true);
+ s.fieldEnd();
+ tracker.expect(3, 3, 3, 3, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ ASSERT_THROW(s.command("_a", {}), LoggableException);
+ ASSERT_THROW(s.command("a:", {}), LoggableException);
+ ASSERT_THROW(s.command("a:_b", {}), LoggableException);
+ tracker.expect(3, 3, 3, 3, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+}
+
+TEST(Stack, multipleFields)
+{
+ tracker.reset();
+ logger.reset();
+ {
+ Stack s{env.context, States::AnyHandlers};
+
+ s.command("a", {{"a", false}});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ EXPECT_EQ("a", s.currentCommandName());
+ EXPECT_EQ(Variant::mapType({{"a", false}}), tracker.startArgs);
+
+ s.fieldStart(false);
+ tracker.expect(1, 0, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ EXPECT_FALSE(tracker.fieldStartIsDefault);
+ EXPECT_EQ(0U, tracker.fieldStartIdx);
+
+ s.data("test");
+ tracker.expect(1, 0, 1, 0, 0, 0, 1); // sc, ec, fsc, fse, asc, aec, dc
+ EXPECT_EQ("test", tracker.dataData);
+
+ s.fieldEnd();
+ tracker.expect(1, 0, 1, 1, 0, 0, 1); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.fieldStart(false);
+ tracker.expect(1, 0, 2, 1, 0, 0, 1); // sc, ec, fsc, fse, asc, aec, dc
+ EXPECT_FALSE(tracker.fieldStartIsDefault);
+ EXPECT_EQ(1U, tracker.fieldStartIdx);
+
+ s.data("test2");
+ tracker.expect(1, 0, 2, 1, 0, 0, 2); // sc, ec, fsc, fse, asc, aec, dc
+ EXPECT_EQ("test2", tracker.dataData);
+
+ s.fieldEnd();
+ tracker.expect(1, 0, 2, 2, 0, 0, 2); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.fieldStart(true);
+ tracker.expect(1, 0, 3, 2, 0, 0, 2); // sc, ec, fsc, fse, asc, aec, dc
+ EXPECT_TRUE(tracker.fieldStartIsDefault);
+ EXPECT_EQ(2U, tracker.fieldStartIdx);
+
+ s.data("test3");
+ tracker.expect(1, 0, 3, 2, 0, 0, 3); // sc, ec, fsc, fse, asc, aec, dc
+ EXPECT_EQ("test3", tracker.dataData);
+
+ s.fieldEnd();
+ tracker.expect(1, 1, 3, 3, 0, 0, 3); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, implicitDefaultFieldOnNewCommand)
+{
+ tracker.reset();
+ logger.reset();
+ {
+ Stack s{env.context, States::AnyHandlers};
+
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.command("b", {});
+ tracker.expect(2, 0, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(2, 2, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, implicitDefaultFieldOnNewCommandWithExplicitDefaultField)
+{
+ tracker.reset();
+ logger.reset();
+ {
+ Stack s{env.context, States::AnyHandlers};
+
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("a", s.currentCommandName());
+
+ s.command("b", {});
+ tracker.expect(2, 0, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("b", s.currentCommandName());
+ s.fieldStart(true);
+ s.fieldEnd();
+ tracker.expect(2, 1, 2, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("a", s.currentCommandName());
+ }
+ tracker.expect(2, 2, 2, 2, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, noImplicitDefaultFieldOnIncompatibleCommand)
+{
+ tracker.reset();
+ logger.reset();
+ {
+ Stack s{env.context, States::AnyHandlers};
+
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("a", s.currentCommandName());
+
+ tracker.fieldStartResult = false;
+ s.command("b", {});
+ tracker.expect(2, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("b", s.currentCommandName());
+ }
+ tracker.expect(2, 2, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, noImplicitDefaultFieldIfDefaultFieldGiven)
+{
+ tracker.reset();
+ logger.reset();
+ {
+ Stack s{env.context, States::AnyHandlers};
+
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("a", s.currentCommandName());
+ s.fieldStart(true);
+ tracker.expect(1, 0, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("a", s.currentCommandName());
+ s.fieldEnd();
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("", s.currentCommandName());
+
+ s.command("b", {});
+ tracker.expect(2, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_EQ("b", s.currentCommandName());
+ }
+ tracker.expect(2, 2, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, implicitDefaultFieldOnData)
+{
+ tracker.reset();
+ logger.reset();
+ {
+ Stack s{env.context, States::AnyHandlers};
+
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.data("test");
+ tracker.expect(1, 0, 1, 0, 0, 0, 1); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(1, 1, 1, 1, 0, 0, 1); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, autoFieldEnd)
+{
+ tracker.reset();
+ logger.reset();
+
+ {
+ Stack s{env.context, States::AnyHandlers};
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(1, 1, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, autoImplicitFieldEnd)
+{
+ tracker.reset();
+ logger.reset();
+
+ {
+ Stack s{env.context, States::AnyHandlers};
+ s.command("a", {});
+ s.command("b", {});
+ s.command("c", {});
+ s.command("d", {});
+ s.command("e", {});
+ s.fieldStart(true);
+ s.fieldEnd();
+ tracker.expect(5, 1, 5, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(5, 5, 5, 5, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, invalidDefaultField)
+{
+ tracker.reset();
+ logger.reset();
+
+ {
+ Stack s{env.context, States::AnyHandlers};
+ s.command("a", {});
+ tracker.fieldStartResult = false;
+ s.fieldStart(true);
+ s.fieldEnd();
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ ASSERT_FALSE(logger.hasError());
+}
+
+TEST(Stack, errorInvalidDefaultFieldData)
+{
+ tracker.reset();
+ logger.reset();
+
+ {
+ Stack s{env.context, States::AnyHandlers};
+ s.command("a", {});
+ tracker.fieldStartResult = false;
+ s.fieldStart(true);
+ ASSERT_FALSE(logger.hasError());
+ s.data("test");
+ ASSERT_TRUE(logger.hasError());
+ s.fieldEnd();
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+}
+
+TEST(Stack, errorInvalidFieldData)
+{
+ tracker.reset();
+ logger.reset();
+
+ {
+ Stack s{env.context, States::AnyHandlers};
+ s.command("a", {});
+ tracker.fieldStartResult = false;
+ ASSERT_FALSE(logger.hasError());
+ s.fieldStart(false);
+ ASSERT_TRUE(logger.hasError());
+ s.data("test");
+ s.fieldEnd();
+ tracker.expect(1, 0, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+}
+
+TEST(Stack, errorFieldStartNoCommand)
+{
+ tracker.reset();
+ logger.reset();
+
+ Stack s{env.context, States::AnyHandlers};
+ ASSERT_THROW(s.fieldStart(false), LoggableException);
+ ASSERT_THROW(s.fieldStart(true), LoggableException);
+ tracker.expect(0, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+}
+
+TEST(Stack, errorMutlipleFieldStarts)
+{
+ tracker.reset();
+ logger.reset();
+
+ {
+ Stack s{env.context, States::AnyHandlers};
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.fieldStart(false);
+ ASSERT_FALSE(logger.hasError());
+ s.fieldStart(false);
+ ASSERT_TRUE(logger.hasError());
+ tracker.expect(1, 0, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.fieldEnd();
+ tracker.expect(1, 0, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+}
+
+TEST(Stack, errorMutlipleFieldEnds)
+{
+ tracker.reset();
+ logger.reset();
+
+ {
+ Stack s{env.context, States::AnyHandlers};
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.fieldStart(false);
+ s.fieldEnd();
+ ASSERT_FALSE(logger.hasError());
+ tracker.expect(1, 0, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ s.fieldEnd();
+ ASSERT_TRUE(logger.hasError());
+ tracker.expect(1, 0, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+ }
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+}
+
+TEST(Stack, errorOpenField)
+{
+ tracker.reset();
+ logger.reset();
+
+ {
+ Stack s{env.context, States::AnyHandlers};
+ s.command("a", {});
+ tracker.expect(1, 0, 0, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+
+ s.fieldStart(false);
+ ASSERT_FALSE(logger.hasError());
+ }
+ ASSERT_TRUE(logger.hasError());
+ tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc
+}
+}
+}
+