diff --git a/include/plasp/pddl/Description.h b/include/plasp/pddl/Description.h index e13be7c..bc2d279 100644 --- a/include/plasp/pddl/Description.h +++ b/include/plasp/pddl/Description.h @@ -28,7 +28,7 @@ class Description const Domain &domain() const; private: - Description(std::istream &istream); + Description(); void parseContent(); void parseSection(); diff --git a/include/plasp/sas/Description.h b/include/plasp/sas/Description.h index ae310e3..9f16c0a 100644 --- a/include/plasp/sas/Description.h +++ b/include/plasp/sas/Description.h @@ -47,6 +47,8 @@ class Description private: Description(); + void parseContent(utils::Parser &parser); + void parseVersionSection(utils::Parser &parser) const; void parseMetricSection(utils::Parser &parser); void parseVariablesSection(utils::Parser &parser); diff --git a/include/plasp/utils/Logger.h b/include/plasp/utils/Logger.h index c8b47f9..1dfc106 100644 --- a/include/plasp/utils/Logger.h +++ b/include/plasp/utils/Logger.h @@ -23,7 +23,7 @@ class Logger void setPedantic(bool isPedantic = true); - void parserWarning(const Parser &parser, const std::string &text); + void parserWarning(const Parser &parser, const std::string &message); private: bool m_isPedantic; diff --git a/include/plasp/utils/Parser.h b/include/plasp/utils/Parser.h index 03bc69c..871715b 100644 --- a/include/plasp/utils/Parser.h +++ b/include/plasp/utils/Parser.h @@ -3,8 +3,11 @@ #include #include +#include #include +#include + namespace plasp { namespace utils @@ -19,22 +22,39 @@ namespace utils class Parser { public: - explicit Parser(std::istream &istream); + using Position = std::stringstream::pos_type; - void setFileName(std::string fileName); - const std::string &fileName() const; + struct Coordinate + { + std::string sectionName; + size_t row; + size_t column; + }; - void resetPosition(); + struct StreamDelimiter + { + Position position; + std::string sectionName; + }; - size_t row() const; - size_t column() const; + public: + explicit Parser(); + explicit Parser(std::string streamName, std::istream &istream); + + void readStream(std::string streamName, std::istream &istream); + void readFile(const boost::filesystem::path &path); + + void reset(); + void seek(Position position); + Position position() const; + Coordinate coordinate() const; void setCaseSensitive(bool isCaseInsensitive = true); char currentCharacter() const; void advance(); bool advanceIf(char expectedCharacter); - bool atEndOfFile() const; + bool atEndOfStream() const; template Type parse(); @@ -64,16 +84,11 @@ class Parser uint64_t parseIntegerBody(); - std::istream &m_istream; - std::string m_fileName; - std::istreambuf_iterator m_position; + mutable std::stringstream m_stream; - size_t m_row; - size_t m_column; + std::vector m_streamDelimiters; bool m_isCaseSensitive; - - bool m_atEndOfFile; }; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -116,7 +131,7 @@ void Parser::skipWhiteSpace(WhiteSpacePredicate whiteSpacePredicate) { checkStream(); - while (!atEndOfFile() && whiteSpacePredicate(currentCharacter())) + while (!atEndOfStream() && whiteSpacePredicate(currentCharacter())) advance(); } diff --git a/include/plasp/utils/ParserException.h b/include/plasp/utils/ParserException.h index bd15682..f2c56bb 100644 --- a/include/plasp/utils/ParserException.h +++ b/include/plasp/utils/ParserException.h @@ -31,8 +31,10 @@ class ParserException: public std::exception } explicit ParserException(const utils::Parser &parser, const std::string &message) - : m_message{parser.fileName() + ":" + std::to_string(parser.row()) + ":" + std::to_string(parser.column()) + " " + message} { + const auto coordinate = parser.coordinate(); + + m_message = coordinate.sectionName + ":" + std::to_string(coordinate.row) + ":" + std::to_string(coordinate.column) + " " + message; } ~ParserException() throw() diff --git a/include/plasp/utils/ParserWarning.h b/include/plasp/utils/ParserWarning.h index cc0842c..9cf9c20 100644 --- a/include/plasp/utils/ParserWarning.h +++ b/include/plasp/utils/ParserWarning.h @@ -31,8 +31,10 @@ class ParserWarning: public std::exception } explicit ParserWarning(const utils::Parser &parser, const std::string &message) - : m_message{parser.fileName() + ":" + std::to_string(parser.row()) + ":" + std::to_string(parser.column()) + " " + message} { + const auto coordinate = parser.coordinate(); + + m_message = coordinate.sectionName + ":" + std::to_string(coordinate.row) + ":" + std::to_string(coordinate.column) + " " + message; } ~ParserWarning() throw() diff --git a/src/plasp/pddl/Description.cpp b/src/plasp/pddl/Description.cpp index 27d2557..f1d2779 100644 --- a/src/plasp/pddl/Description.cpp +++ b/src/plasp/pddl/Description.cpp @@ -18,9 +18,8 @@ namespace pddl // //////////////////////////////////////////////////////////////////////////////////////////////////// -Description::Description(std::istream &istream) -: m_parser(istream), - m_context(m_parser), +Description::Description() +: m_context(m_parser), m_domain{std::make_unique(Domain(m_context))}, m_problem{std::make_unique(Problem(m_context, *m_domain))} { @@ -31,9 +30,9 @@ Description::Description(std::istream &istream) Description Description::fromStream(std::istream &istream) { - Description description(istream); + Description description; - description.m_parser.setFileName("std::cin"); + description.m_parser.readStream("std::cin", istream); description.parseContent(); description.checkConsistency(); @@ -47,29 +46,15 @@ Description Description::fromFiles(const std::vector &paths) { BOOST_ASSERT(!paths.empty()); - std::for_each(paths.cbegin(), paths.cend(), - [&](const auto &path) - { - if (!boost::filesystem::is_regular_file(path)) - throw std::runtime_error("File does not exist: \"" + path + "\""); - }); - - std::ifstream fileStream; - Description description(fileStream); + Description description; std::for_each(paths.cbegin(), paths.cend(), [&](const auto &path) { - fileStream.close(); - fileStream.clear(); - fileStream.open(path, std::ios::in); - - description.m_parser.setFileName(path); - description.m_parser.resetPosition(); - - description.parseContent(); + description.m_parser.readFile(path); }); + description.parseContent(); description.checkConsistency(); return description; @@ -92,7 +77,7 @@ void Description::parseContent() { m_context.parser.skipWhiteSpace(); - if (m_context.parser.atEndOfFile()) + if (m_context.parser.atEndOfStream()) return; m_context.parser.expect("("); diff --git a/src/plasp/sas/Description.cpp b/src/plasp/sas/Description.cpp index eb98490..75de27a 100644 --- a/src/plasp/sas/Description.cpp +++ b/src/plasp/sas/Description.cpp @@ -30,18 +30,11 @@ Description::Description() Description Description::fromStream(std::istream &istream) { + utils::Parser parser; + parser.readStream("std::cin", istream); + Description description; - - utils::Parser parser(istream); - - description.parseVersionSection(parser); - description.parseMetricSection(parser); - description.parseVariablesSection(parser); - description.parseMutexSection(parser); - description.parseInitialStateSection(parser); - description.parseGoalSection(parser); - description.parseOperatorSection(parser); - description.parseAxiomSection(parser); + description.parseContent(parser); return description; } @@ -53,9 +46,13 @@ Description Description::fromFile(const boost::filesystem::path &path) if (!boost::filesystem::is_regular_file(path)) throw std::runtime_error("File does not exist: \"" + path.string() + "\""); - std::ifstream fileStream(path.string(), std::ios::in); + utils::Parser parser; + parser.readFile(path); - return Description::fromStream(fileStream); + Description description; + description.parseContent(parser); + + return description; } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -146,6 +143,20 @@ bool Description::usesConditionalEffects() const //////////////////////////////////////////////////////////////////////////////////////////////////// +void Description::parseContent(utils::Parser &parser) +{ + parseVersionSection(parser); + parseMetricSection(parser); + parseVariablesSection(parser); + parseMutexSection(parser); + parseInitialStateSection(parser); + parseGoalSection(parser); + parseOperatorSection(parser); + parseAxiomSection(parser); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + void Description::parseVersionSection(utils::Parser &parser) const { parser.expect("begin_version"); diff --git a/src/plasp/utils/Logger.cpp b/src/plasp/utils/Logger.cpp index f66d29b..cef7941 100644 --- a/src/plasp/utils/Logger.cpp +++ b/src/plasp/utils/Logger.cpp @@ -27,12 +27,16 @@ void Logger::setPedantic(bool isPedantic) //////////////////////////////////////////////////////////////////////////////////////////////////// -void Logger::parserWarning(const Parser &parser, const std::string &text) +void Logger::parserWarning(const Parser &parser, const std::string &message) { if (m_isPedantic) - throw ParserWarning(parser, text); + throw ParserWarning(parser, message); - std::cerr << "Warning: " << parser.fileName() << ":" << parser.row() << ":" << parser.column() << " " << text << std::endl; + const auto coordinate = parser.coordinate(); + + std::cerr << "Warning: " << coordinate.sectionName << ":" + << std::to_string(coordinate.row) + ":" + std::to_string(coordinate.column) + << " " << message << std::endl; } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/plasp/utils/Parser.cpp b/src/plasp/utils/Parser.cpp index ac15593..60dc7e5 100644 --- a/src/plasp/utils/Parser.cpp +++ b/src/plasp/utils/Parser.cpp @@ -1,6 +1,7 @@ #include #include +#include #include @@ -21,56 +22,116 @@ const std::istreambuf_iterator Parser::EndOfFile = std::istreambuf_iterato //////////////////////////////////////////////////////////////////////////////////////////////////// -Parser::Parser(std::istream &istream) -: m_istream(istream), - m_position(m_istream), - m_row{1}, - m_column{1}, - m_isCaseSensitive{true}, - m_atEndOfFile{false} +Parser::Parser() +: m_isCaseSensitive{true} { std::setlocale(LC_NUMERIC, "C"); // Don’t skip whitespace - istream.exceptions(std::istream::badbit); + m_stream.exceptions(std::istream::badbit); } //////////////////////////////////////////////////////////////////////////////////////////////////// -void Parser::setFileName(std::string fileName) +Parser::Parser(std::string streamName, std::istream &istream) +: Parser() { - m_fileName = fileName; + readStream(streamName, istream); } //////////////////////////////////////////////////////////////////////////////////////////////////// -const std::string &Parser::fileName() const +void Parser::readStream(std::string streamName, std::istream &istream) { - return m_fileName; + // Store position of new section + const auto position = m_stream.tellp(); + + m_streamDelimiters.push_back({position, streamName}); + + m_stream << istream.rdbuf(); } //////////////////////////////////////////////////////////////////////////////////////////////////// -void Parser::resetPosition() +void Parser::readFile(const boost::filesystem::path &path) { - m_row = 1; - m_column = 1; - m_atEndOfFile = false; - m_position = std::istreambuf_iterator(m_istream); + if (!boost::filesystem::is_regular_file(path)) + throw std::runtime_error("File does not exist: \"" + path.string() + "\""); + + std::ifstream fileStream(path.string(), std::ios::in); + + readStream(path.string(), fileStream); } //////////////////////////////////////////////////////////////////////////////////////////////////// -size_t Parser::row() const +void Parser::reset() { - return m_row; + m_stream.clear(); + seek(std::ios::beg); } //////////////////////////////////////////////////////////////////////////////////////////////////// -size_t Parser::column() const +void Parser::seek(Position position) { - return m_column; + m_stream.clear(); + m_stream.seekg(position); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +Parser::Position Parser::position() const +{ + return m_stream.tellg(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +Parser::Coordinate Parser::coordinate() const +{ + const auto currentPosition = position(); + + // Find current section + auto currentFile = std::find_if(m_streamDelimiters.crbegin(), m_streamDelimiters.crend(), + [&](const auto &fileDelimiter) + { + return currentPosition >= fileDelimiter.position; + }); + + // If the parser is at the end of the stream, still count from the beginning of the last section + if (currentFile == m_streamDelimiters.crend()) + currentFile = m_streamDelimiters.crbegin(); + + // Go back to beginning of section + m_stream.clear(); + m_stream.seekg(currentFile->position); + + size_t row = 1; + size_t column = 1; + + // Compute the coordinate character by character + while (true) + { + if (currentPosition == -1 && atEndOfStream()) + break; + else if (currentPosition >= 0 && position() >= currentPosition) + break; + + const auto character = currentCharacter(); + + if (character == '\n') + { + row++; + column = 1; + } + else if (std::isblank(character) || std::isprint(character)) + column++; + + m_stream.ignore(1); + } + + return {currentFile->sectionName, row, column}; } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -84,29 +145,27 @@ void Parser::setCaseSensitive(bool isCaseSensitive) char Parser::currentCharacter() const { - checkStream(); - if (m_isCaseSensitive) - return *m_position; + return m_stream.peek(); - return std::tolower(*m_position); + return std::tolower(m_stream.peek()); } //////////////////////////////////////////////////////////////////////////////////////////////////// -bool Parser::atEndOfFile() const +bool Parser::atEndOfStream() const { - return m_position.equal(EndOfFile); + return position() == -1; } //////////////////////////////////////////////////////////////////////////////////////////////////// void Parser::checkStream() const { - if (atEndOfFile()) + if (atEndOfStream()) throw ParserException(*this, "Reading past end of file"); - if (m_istream.fail()) + if (m_stream.fail()) throw ParserException(*this); } @@ -115,18 +174,7 @@ void Parser::checkStream() const void Parser::advance() { checkStream(); - - const auto character = currentCharacter(); - - if (character == '\n') - { - m_row++; - m_column = 1; - } - else if (std::isblank(character) || std::isprint(character)) - m_column++; - - m_position++; + m_stream.ignore(1); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -246,7 +294,7 @@ uint64_t Parser::parseIntegerBody() uint64_t value = 0; - while (!atEndOfFile()) + while (!atEndOfStream()) { const auto character = currentCharacter(); diff --git a/tests/TestUtils.cpp b/tests/TestUtils.cpp index 2e529de..192bef2 100644 --- a/tests/TestUtils.cpp +++ b/tests/TestUtils.cpp @@ -9,7 +9,7 @@ TEST(UtilsTests, ParseSimple) { std::stringstream s("identifier 5 \n-51\t 0 1 expected unexpected"); - plasp::utils::Parser p(s); + plasp::utils::Parser p("input", s); ASSERT_EQ(p.parse(), "identifier"); ASSERT_EQ(p.parse(), 5u); @@ -19,6 +19,8 @@ TEST(UtilsTests, ParseSimple) ASSERT_NO_THROW(p.expect("expected")); ASSERT_THROW(p.expect("expected"), plasp::utils::ParserException); + + // TODO: test case-insensitive input } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -26,7 +28,7 @@ TEST(UtilsTests, ParseSimple) TEST(UtilsTests, ParseUnsignedNumbers) { std::stringstream s("100 200 -300 -400"); - plasp::utils::Parser p(s); + plasp::utils::Parser p("input", s); ASSERT_EQ(p.parse(), 100); ASSERT_EQ(p.parse(), 200u); @@ -39,13 +41,13 @@ TEST(UtilsTests, ParseUnsignedNumbers) TEST(UtilsTests, ParseEndOfFile) { std::stringstream s1("test"); - plasp::utils::Parser p1(s1); + plasp::utils::Parser p1("input", s1); ASSERT_NO_THROW(p1.expect("test")); ASSERT_THROW(p1.parse(), plasp::utils::ParserException); std::stringstream s2("test1 test2 test3"); - plasp::utils::Parser p2(s2); + plasp::utils::Parser p2("input", s2); ASSERT_NO_THROW(p2.expect("test1")); ASSERT_NO_THROW(p2.expect("test2")); @@ -53,13 +55,13 @@ TEST(UtilsTests, ParseEndOfFile) ASSERT_THROW(p2.parse(), plasp::utils::ParserException); std::stringstream s3("-127"); - plasp::utils::Parser p3(s3); + plasp::utils::Parser p3("input", s3); p3.expect(-127); ASSERT_THROW(p3.parse(), plasp::utils::ParserException); std::stringstream s4("128 -1023 -4095"); - plasp::utils::Parser p4(s4); + plasp::utils::Parser p4("input", s4); ASSERT_NO_THROW(p4.expect(128)); ASSERT_NO_THROW(p4.expect(-1023)); @@ -67,13 +69,13 @@ TEST(UtilsTests, ParseEndOfFile) ASSERT_THROW(p4.parse(), plasp::utils::ParserException); std::stringstream s5("0"); - plasp::utils::Parser p5(s5); + plasp::utils::Parser p5("input", s5); p5.expect(false); ASSERT_THROW(p5.parse(), plasp::utils::ParserException); std::stringstream s6("0 1 0"); - plasp::utils::Parser p6(s6); + plasp::utils::Parser p6("input", s6); ASSERT_NO_THROW(p6.expect(false)); ASSERT_NO_THROW(p6.expect(true)); @@ -86,74 +88,90 @@ TEST(UtilsTests, ParseEndOfFile) TEST(UtilsTests, ParserPosition) { std::stringstream s("123 \n4\ntest1\n test2\ntest3 \ntest4\n\n\n\n"); - plasp::utils::Parser p(s); + plasp::utils::Parser p("input", s); - ASSERT_EQ(p.row(), 1u); - ASSERT_EQ(p.column(), 1u); + plasp::utils::Parser::Coordinate c; + + c = p.coordinate(); + ASSERT_EQ(c.row, 1u); + ASSERT_EQ(c.column, 1u); ASSERT_EQ(p.currentCharacter(), '1'); ASSERT_NO_THROW(p.advance()); - ASSERT_EQ(p.row(), 1u); - ASSERT_EQ(p.column(), 2u); + c = p.coordinate(); + ASSERT_EQ(c.row, 1u); + ASSERT_EQ(c.column, 2u); ASSERT_EQ(p.currentCharacter(), '2'); ASSERT_NO_THROW(p.advance()); - ASSERT_EQ(p.row(), 1u); - ASSERT_EQ(p.column(), 3u); + c = p.coordinate(); + ASSERT_EQ(c.row, 1u); + ASSERT_EQ(c.column, 3u); ASSERT_EQ(p.currentCharacter(), '3'); ASSERT_NO_THROW(p.advance()); - ASSERT_EQ(p.row(), 1u); - ASSERT_EQ(p.column(), 4u); + c = p.coordinate(); + ASSERT_EQ(c.row, 1u); + ASSERT_EQ(c.column, 4u); ASSERT_EQ(p.currentCharacter(), ' '); ASSERT_NO_THROW(p.advance()); - ASSERT_EQ(p.row(), 1u); - ASSERT_EQ(p.column(), 5u); + c = p.coordinate(); + ASSERT_EQ(c.row, 1u); + ASSERT_EQ(c.column, 5u); ASSERT_EQ(p.currentCharacter(), '\n'); ASSERT_NO_THROW(p.advance()); - ASSERT_EQ(p.row(), 2u); - ASSERT_EQ(p.column(), 1u); + c = p.coordinate(); + ASSERT_EQ(c.row, 2u); + ASSERT_EQ(c.column, 1u); ASSERT_EQ(p.currentCharacter(), '4'); ASSERT_NO_THROW(p.advance()); ASSERT_NO_THROW(p.expect("test1")); - ASSERT_EQ(p.row(), 3u); - ASSERT_EQ(p.column(), 6u); + c = p.coordinate(); + ASSERT_EQ(c.row, 3u); + ASSERT_EQ(c.column, 6u); ASSERT_NO_THROW(p.expect("test2")); - ASSERT_EQ(p.row(), 4u); - ASSERT_EQ(p.column(), 7u); + c = p.coordinate(); + ASSERT_EQ(c.row, 4u); + ASSERT_EQ(c.column, 7u); ASSERT_NO_THROW(p.expect("test3")); - ASSERT_EQ(p.row(), 5u); - ASSERT_EQ(p.column(), 6u); + c = p.coordinate(); + ASSERT_EQ(c.row, 5u); + ASSERT_EQ(c.column, 6u); ASSERT_NO_THROW(p.skipLine()); - ASSERT_EQ(p.row(), 6u); - ASSERT_EQ(p.column(), 1u); + c = p.coordinate(); + ASSERT_EQ(c.row, 6u); + ASSERT_EQ(c.column, 1u); ASSERT_NO_THROW(p.skipLine()); - ASSERT_EQ(p.row(), 7u); - ASSERT_EQ(p.column(), 1u); + c = p.coordinate(); + ASSERT_EQ(c.row, 7u); + ASSERT_EQ(c.column, 1u); ASSERT_NO_THROW(p.skipWhiteSpace()); - ASSERT_TRUE(p.atEndOfFile()); - ASSERT_EQ(p.row(), 10u); - ASSERT_EQ(p.column(), 1u); + c = p.coordinate(); + ASSERT_EQ(c.row, 10u); + ASSERT_EQ(c.column, 1u); + ASSERT_TRUE(p.atEndOfStream()); + + // TODO: test parser with multiple sections } ////////////////////////////////////////////////////////////////////////////////////////////////////