diff --git a/include/plasp/pddl/ConsistencyException.h b/include/plasp/pddl/ConsistencyException.h new file mode 100644 index 0000000..a8ec931 --- /dev/null +++ b/include/plasp/pddl/ConsistencyException.h @@ -0,0 +1,57 @@ +#ifndef __PLASP__PDDL__CONSISTENCY_EXCEPTION_H +#define __PLASP__PDDL__CONSISTENCY_EXCEPTION_H + +#include +#include + +namespace plasp +{ +namespace pddl +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// ConsistencyException +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class ConsistencyException: public std::exception +{ + public: + explicit ConsistencyException() + : ConsistencyException("Unspecified consistency error") + { + } + + explicit ConsistencyException(const char *message) + : ConsistencyException(static_cast(message)) + { + } + + explicit ConsistencyException(const std::string &message) + : m_message{message} + { + } + + ~ConsistencyException() throw() + { + } + + const char *what() const throw() + { + if (m_message.empty()) + return "Unspecified consistency error"; + + return m_message.c_str(); + } + + private: + std::string m_message; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} +} + +#endif diff --git a/include/plasp/pddl/Domain.h b/include/plasp/pddl/Domain.h index 1c059cb..fb06477 100644 --- a/include/plasp/pddl/Domain.h +++ b/include/plasp/pddl/Domain.h @@ -1,7 +1,10 @@ #ifndef __PLASP__PDDL__DOMAIN_H #define __PLASP__PDDL__DOMAIN_H +#include + #include +#include #include namespace plasp @@ -20,9 +23,12 @@ class Domain public: static Domain fromPDDL(utils::Parser &parser); + using TypesHashMap = std::unordered_map; + public: const std::string &name() const; const Requirement::Types &requirements() const; + const TypesHashMap &types() const; private: Domain() = default; @@ -30,10 +36,16 @@ class Domain void parseSection(utils::Parser &parser); void parseRequirementsSection(utils::Parser &parser); + bool hasRequirement(Requirement::Type requirement) const; void computeDerivedRequirements(); + void parseTypingSection(utils::Parser &parser); + + void checkConsistency(); + std::string m_name; Requirement::Types m_requirements; + TypesHashMap m_types; }; //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/include/plasp/pddl/Type.h b/include/plasp/pddl/Type.h new file mode 100644 index 0000000..31dada2 --- /dev/null +++ b/include/plasp/pddl/Type.h @@ -0,0 +1,48 @@ +#ifndef __PLASP__PDDL__TYPE_H +#define __PLASP__PDDL__TYPE_H + +#include +#include +#include + +#include + +namespace plasp +{ +namespace pddl +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Type +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class Type +{ + public: + Type(std::string name); + + public: + void setDirty(bool isDirty = true); + bool isDirty() const; + + const std::string &name() const; + + void addParentType(const Type &parentType); + const std::vector &parentTypes() const; + + private: + bool m_isDirty; + + std::string m_name; + + std::vector m_parentTypes; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} +} + +#endif diff --git a/include/plasp/utils/Parser.h b/include/plasp/utils/Parser.h index e65c13b..d41917c 100644 --- a/include/plasp/utils/Parser.h +++ b/include/plasp/utils/Parser.h @@ -26,6 +26,7 @@ class Parser char currentCharacter() const; void advance(); + bool advanceIf(char expectedCharacter); bool atEndOfFile() const; template @@ -54,8 +55,6 @@ class Parser private: void checkStream() const; - bool advanceIf(char expectedCharacter); - uint64_t parseIntegerBody(); std::istream &m_istream; diff --git a/src/plasp/pddl/Domain.cpp b/src/plasp/pddl/Domain.cpp index d002a35..fbbb706 100644 --- a/src/plasp/pddl/Domain.cpp +++ b/src/plasp/pddl/Domain.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -38,6 +39,8 @@ Domain Domain::fromPDDL(utils::Parser &parser) domain.computeDerivedRequirements(); + domain.checkConsistency(); + return domain; } @@ -57,6 +60,13 @@ const Requirement::Types &Domain::requirements() const //////////////////////////////////////////////////////////////////////////////////////////////////// +const Domain::TypesHashMap &Domain::types() const +{ + return m_types; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + void Domain::parseSection(utils::Parser &parser) { parser.expect("(:"); @@ -90,7 +100,7 @@ void Domain::parseSection(utils::Parser &parser) if (sectionIdentifier == "requirements") parseRequirementsSection(parser); else if (sectionIdentifier == "types") - skipSection(); + parseTypingSection(parser); else if (sectionIdentifier == "constants") skipSection(); else if (sectionIdentifier == "predicates") @@ -128,16 +138,17 @@ void Domain::parseRequirementsSection(utils::Parser &parser) //////////////////////////////////////////////////////////////////////////////////////////////////// +bool Domain::hasRequirement(Requirement::Type requirement) const +{ + const auto match = std::find(m_requirements.cbegin(), m_requirements.cend(), requirement); + + return match != m_requirements.cend(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + void Domain::computeDerivedRequirements() { - const auto hasRequirement = - [&](const auto requirement) - { - const auto match = std::find(m_requirements.cbegin(), m_requirements.cend(), requirement); - - return match != m_requirements.cend(); - }; - const auto addRequirementUnique = [&](const auto requirement) { @@ -180,5 +191,68 @@ void Domain::computeDerivedRequirements() //////////////////////////////////////////////////////////////////////////////////////////////////// +void Domain::parseTypingSection(utils::Parser &parser) +{ + // Parses a single type identifier + const auto parseType = + [&]() -> auto & + { + parser.skipWhiteSpace(); + + const auto typeName = parser.parseIdentifier(isIdentifier); + const auto insertionResult = m_types.emplace(std::make_pair(typeName, Type(typeName))); + auto &type = insertionResult.first->second; + + // Flag type for potentially upcoming parent type declaration + type.setDirty(); + + parser.skipWhiteSpace(); + + return type; + }; + + // Parses a type and potentially its parent type + while (parser.currentCharacter() != ')') + { + parseType(); + + // Check for type inheritance + if (!parser.advanceIf('-')) + continue; + + // If existing, parse parent type + auto &parentType = parseType(); + + parentType.setDirty(false); + + // Assign parent type to all types that were previously flagged + std::for_each(m_types.begin(), m_types.end(), + [&](auto &childType) + { + if (!childType.second.isDirty()) + return; + + childType.second.addParentType(parentType); + childType.second.setDirty(false); + }); + } + + parser.expect(")"); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void Domain::checkConsistency() +{ + if (!m_types.empty() && !hasRequirement(Requirement::Type::Typing)) + { + throw ConsistencyException("Domain contains typing information but does not declare typing requirement"); + + m_requirements.push_back(Requirement::Type::Typing); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + } } diff --git a/src/plasp/pddl/Type.cpp b/src/plasp/pddl/Type.cpp new file mode 100644 index 0000000..ce8cf71 --- /dev/null +++ b/src/plasp/pddl/Type.cpp @@ -0,0 +1,58 @@ +#include + +namespace plasp +{ +namespace pddl +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Type +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +Type::Type(std::string name) +: m_isDirty{false}, + m_name{name} +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void Type::setDirty(bool isDirty) +{ + m_isDirty = isDirty; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool Type::isDirty() const +{ + return m_isDirty; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const std::string &Type::name() const +{ + return m_name; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void Type::addParentType(const Type &parentType) +{ + m_parentTypes.emplace_back(&parentType); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const std::vector &Type::parentTypes() const +{ + return m_parentTypes; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} +} diff --git a/tests/TestPDDLParser.cpp b/tests/TestPDDLParser.cpp index c8207b1..6e0a87d 100644 --- a/tests/TestPDDLParser.cpp +++ b/tests/TestPDDLParser.cpp @@ -13,7 +13,8 @@ class PDDLParserTests : public ::testing::Test { protected: PDDLParserTests() - : m_blocksworldDomainFile(readFile("data/blocksworld-domain.pddl")) + : m_blocksworldDomainFile(readFile("data/blocksworld-domain.pddl")), + m_storageDomainFile(readFile("data/storage-domain.pddl")) { } @@ -32,11 +33,12 @@ class PDDLParserTests : public ::testing::Test } std::stringstream m_blocksworldDomainFile; + std::stringstream m_storageDomainFile; }; //////////////////////////////////////////////////////////////////////////////////////////////////// -TEST_F(PDDLParserTests, ParseValidPDDLFile) +TEST_F(PDDLParserTests, ParseBlocksWorldDomain) { try { @@ -46,9 +48,62 @@ TEST_F(PDDLParserTests, ParseValidPDDLFile) const auto &domain = description.domain(); + ASSERT_EQ(domain.name(), "BLOCKS"); + ASSERT_EQ(domain.requirements().size(), 2u); ASSERT_EQ(domain.requirements()[0], plasp::pddl::Requirement::Type::STRIPS); ASSERT_EQ(domain.requirements()[1], plasp::pddl::Requirement::Type::Typing); + + ASSERT_EQ(domain.types().size(), 1u); + + const auto blockType = domain.types().find("block"); + ASSERT_NE(blockType, domain.types().cend()); + + ASSERT_EQ(blockType->second.name(), "block"); + ASSERT_EQ(blockType->second.parentTypes().size(), 0u); + } + catch (const std::exception &e) + { + FAIL() << e.what(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TEST_F(PDDLParserTests, ParseStorageDomain) +{ + try + { + const auto description = plasp::pddl::Description::fromStream(m_storageDomainFile); + + ASSERT_NO_THROW(description.domain()); + + const auto &domain = description.domain(); + + ASSERT_EQ(domain.name(), "Storage-Propositional"); + + ASSERT_EQ(domain.requirements().size(), 1u); + ASSERT_EQ(domain.requirements()[0], plasp::pddl::Requirement::Type::Typing); + + const auto area = domain.types().find("area"); + ASSERT_NE(area, domain.types().cend()); + const auto hoist = domain.types().find("hoist"); + ASSERT_NE(hoist, domain.types().cend()); + const auto object = domain.types().find("object"); + ASSERT_NE(object, domain.types().cend()); + const auto storearea = domain.types().find("storearea"); + ASSERT_NE(storearea, domain.types().cend()); + const auto surface= domain.types().find("surface"); + ASSERT_NE(surface, domain.types().cend()); + + const auto &hoistParents = hoist->second.parentTypes(); + ASSERT_EQ(hoistParents.size(), 1u); + ASSERT_TRUE(std::find(hoistParents.cbegin(), hoistParents.cend(), &object->second) != hoistParents.cend()); + + const auto &areaParents = area->second.parentTypes(); + ASSERT_EQ(areaParents.size(), 2u); + ASSERT_TRUE(std::find(areaParents.cbegin(), areaParents.cend(), &object->second) != areaParents.cend()); + ASSERT_TRUE(std::find(areaParents.cbegin(), areaParents.cend(), &surface->second) != areaParents.cend()); } catch (const std::exception &e) { diff --git a/tests/data/storage-domain.pddl b/tests/data/storage-domain.pddl new file mode 100644 index 0000000..c001130 --- /dev/null +++ b/tests/data/storage-domain.pddl @@ -0,0 +1,46 @@ +(define (domain Storage-Propositional) +(:requirements :typing) +(:types hoist surface place area - object + container depot - place + storearea transitarea - area + area crate - surface) + +(:predicates (clear ?s - storearea) + (in ?x - (either storearea crate) ?p - place) + (available ?h - hoist) + (lifting ?h - hoist ?c - crate) + (at ?h - hoist ?a - area) + (on ?c - crate ?s - storearea) + (connected ?a1 ?a2 - area) + (compatible ?c1 ?c2 - crate)) + +(:action lift + :parameters (?h - hoist ?c - crate ?a1 - storearea ?a2 - area ?p - place) + :precondition (and (connected ?a1 ?a2) (at ?h ?a2) (available ?h) + (on ?c ?a1) (in ?a1 ?p)) + :effect (and (not (on ?c ?a1)) (clear ?a1) + (not (available ?h)) (lifting ?h ?c) (not (in ?c ?p)))) + +(:action drop + :parameters (?h - hoist ?c - crate ?a1 - storearea ?a2 - area ?p - place) + :precondition (and (connected ?a1 ?a2) (at ?h ?a2) (lifting ?h ?c) + (clear ?a1) (in ?a1 ?p)) + :effect (and (not (lifting ?h ?c)) (available ?h) + (not (clear ?a1)) (on ?c ?a1) (in ?c ?p))) + +(:action move + :parameters (?h - hoist ?from ?to - storearea) + :precondition (and (at ?h ?from) (clear ?to) (connected ?from ?to)) + :effect (and (not (at ?h ?from)) (at ?h ?to) (not (clear ?to)) (clear ?from))) + +(:action go-out + :parameters (?h - hoist ?from - storearea ?to - transitarea) + :precondition (and (at ?h ?from) (connected ?from ?to)) + :effect (and (not (at ?h ?from)) (at ?h ?to) (clear ?from))) + +(:action go-in + :parameters (?h - hoist ?from - transitarea ?to - storearea) + :precondition (and (at ?h ?from) (connected ?from ?to) (clear ?to)) + :effect (and (not (at ?h ?from)) (at ?h ?to) (not (clear ?to)))) +) +