diff --git a/src/ZoneCodeGeneratorLib/Parsing/Impl/IncludingStreamProxy.cpp b/src/ZoneCodeGeneratorLib/Parsing/Impl/IncludingStreamProxy.cpp index b006bb1e..c141f65f 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Impl/IncludingStreamProxy.cpp +++ b/src/ZoneCodeGeneratorLib/Parsing/Impl/IncludingStreamProxy.cpp @@ -37,6 +37,13 @@ bool IncludingStreamProxy::ExtractIncludeFilename(const ParserLine& line, const { auto currentPos = includeDirectivePosition; bool isDoubleQuotes; + + while(isspace(line.m_line[currentPos])) + { + if(currentPos++ >= line.m_line.size()) + throw ParsingException(TokenPos(line.m_filename, line.m_line_number, currentPos), INCLUDE_QUOTES_ERROR); + } + if (line.m_line[currentPos] == '"') isDoubleQuotes = true; else if (line.m_line[currentPos] == '<') diff --git a/test/ZoneCodeGeneratorLibTests/Parsing/Impl/IncludingStreamProxyTest.cpp b/test/ZoneCodeGeneratorLibTests/Parsing/Impl/IncludingStreamProxyTest.cpp index e69de29b..f551a78b 100644 --- a/test/ZoneCodeGeneratorLibTests/Parsing/Impl/IncludingStreamProxyTest.cpp +++ b/test/ZoneCodeGeneratorLibTests/Parsing/Impl/IncludingStreamProxyTest.cpp @@ -0,0 +1,136 @@ +#include + +#include "Parsing/Impl/IncludingStreamProxy.h" +#include "Parsing/Mock/MockParserLineStream.h" + +namespace test::parsing +{ + TEST_CASE("IncludingStreamProxy: Ensure simple include is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "Hello world", + "#include \"ASDF.txt\"", + "and bye" + }; + + const std::vector asdf + { + "Hello galaxy" + }; + + MockParserLineStream mockStream(lines); + mockStream.AddIncludeLines("ASDF.txt", asdf); + + IncludingStreamProxy proxy(&mockStream); + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 1); + REQUIRE(line.m_filename == MockParserLineStream::MOCK_FILENAME); + REQUIRE(line.m_line == "Hello world"); + } + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 1); + REQUIRE(line.m_filename == "ASDF.txt"); + REQUIRE(line.m_line == "Hello galaxy"); + } + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 3); + REQUIRE(line.m_filename == MockParserLineStream::MOCK_FILENAME); + REQUIRE(line.m_line == "and bye"); + } + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("IncludingStreamProxy: Ensure simple include with angle brackets is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "Hello world", + "#include ", + "and bye" + }; + + const std::vector asdf + { + "Hello galaxy" + }; + + MockParserLineStream mockStream(lines); + mockStream.AddIncludeLines("ASDF.txt", asdf); + + IncludingStreamProxy proxy(&mockStream); + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 1); + REQUIRE(line.m_filename == MockParserLineStream::MOCK_FILENAME); + REQUIRE(line.m_line == "Hello world"); + } + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 1); + REQUIRE(line.m_filename == "ASDF.txt"); + REQUIRE(line.m_line == "Hello galaxy"); + } + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 3); + REQUIRE(line.m_filename == MockParserLineStream::MOCK_FILENAME); + REQUIRE(line.m_line == "and bye"); + } + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("IncludingStreamProxy: Ensure can have spaces before include directive", "[parsing][parsingstream]") + { + const std::vector lines + { + "Hello world", + " #include \"ASDF.txt\" ", + "and bye" + }; + + const std::vector asdf + { + "Hello galaxy" + }; + + MockParserLineStream mockStream(lines); + mockStream.AddIncludeLines("ASDF.txt", asdf); + + IncludingStreamProxy proxy(&mockStream); + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 1); + REQUIRE(line.m_filename == MockParserLineStream::MOCK_FILENAME); + REQUIRE(line.m_line == "Hello world"); + } + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 1); + REQUIRE(line.m_filename == "ASDF.txt"); + REQUIRE(line.m_line == "Hello galaxy"); + } + + { + auto line = proxy.NextLine(); + REQUIRE(line.m_line_number == 3); + REQUIRE(line.m_filename == MockParserLineStream::MOCK_FILENAME); + REQUIRE(line.m_line == "and bye"); + } + + REQUIRE(proxy.Eof()); + } +} diff --git a/test/ZoneCodeGeneratorLibTests/Parsing/Mock/MockParserLineStream.cpp b/test/ZoneCodeGeneratorLibTests/Parsing/Mock/MockParserLineStream.cpp index b6287466..d297374f 100644 --- a/test/ZoneCodeGeneratorLibTests/Parsing/Mock/MockParserLineStream.cpp +++ b/test/ZoneCodeGeneratorLibTests/Parsing/Mock/MockParserLineStream.cpp @@ -1,19 +1,40 @@ #include "MockParserLineStream.h" -const std::string MockParserLineStream::MOCK_FILENAME; +#include + +const std::string MockParserLineStream::MOCK_FILENAME = "Mockfile"; MockParserLineStream::MockParserLineStream(const std::vector& lines) - : m_lines(lines), - m_line(0) { + AddIncludeLines(MOCK_FILENAME, lines); + m_include_positions.emplace_back(MOCK_FILENAME, lines); +} + +MockParserLineStream::IncludePos::IncludePos(std::string filename, const std::vector& lines) + : m_filename(std::move(filename)), + m_lines(lines), + m_pos(0) +{ +} + +void MockParserLineStream::AddIncludeLines(const std::string& filename, const std::vector& lines) +{ + m_include_lines[filename] = lines; } ParserLine MockParserLineStream::NextLine() { - if(m_line < m_lines.size()) + while (!m_include_positions.empty()) { - const auto line = m_line++; - return ParserLine(MOCK_FILENAME, line + 1, m_lines[line]); + auto& currentInclude = m_include_positions.back(); + + if (currentInclude.m_pos < currentInclude.m_lines.size()) + { + const auto line = currentInclude.m_pos++; + return ParserLine(currentInclude.m_filename, static_cast(line + 1), currentInclude.m_lines[line]); + } + + m_include_positions.pop_back(); } return ParserLine(MOCK_FILENAME, 0, std::string()); @@ -21,7 +42,11 @@ ParserLine MockParserLineStream::NextLine() bool MockParserLineStream::IncludeFile(const std::string& filename) { - m_includes.push_back(filename); + const auto foundInclude = m_include_lines.find(filename); + if (foundInclude == m_include_lines.end()) + return false; + + m_include_positions.emplace_back(foundInclude->first, foundInclude->second); return true; } @@ -32,5 +57,8 @@ bool MockParserLineStream::IsOpen() const bool MockParserLineStream::Eof() const { - return m_line >= m_lines.size(); + return !std::any_of(m_include_positions.begin(), m_include_positions.end(), [](const IncludePos& pos) + { + return pos.m_pos < pos.m_lines.size(); + }); } diff --git a/test/ZoneCodeGeneratorLibTests/Parsing/Mock/MockParserLineStream.h b/test/ZoneCodeGeneratorLibTests/Parsing/Mock/MockParserLineStream.h index 2e431380..70a7dc62 100644 --- a/test/ZoneCodeGeneratorLibTests/Parsing/Mock/MockParserLineStream.h +++ b/test/ZoneCodeGeneratorLibTests/Parsing/Mock/MockParserLineStream.h @@ -1,20 +1,33 @@ #pragma once #include +#include #include "Parsing/IParserLineStream.h" class MockParserLineStream final : public IParserLineStream { +public: static const std::string MOCK_FILENAME; - const std::vector& m_lines; - unsigned m_line; - std::vector m_includes{}; +private: + class IncludePos + { + public: + std::string m_filename; + const std::vector& m_lines; + unsigned m_pos; + + IncludePos(std::string filename, const std::vector& lines); + }; + + std::unordered_map> m_include_lines; + std::vector m_include_positions; public: explicit MockParserLineStream(const std::vector& lines); + void AddIncludeLines(const std::string& filename, const std::vector& lines); ParserLine NextLine() override; bool IncludeFile(const std::string& filename) override; _NODISCARD bool IsOpen() const override;