From 1822979a8b8d4ee3ccb59eb16c2236294c703994 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 10 Feb 2021 23:46:15 +0100 Subject: [PATCH] Add implementation for ZCG cpp defines proxy --- .../Parsing/Header/HeaderFileReader.cpp | 33 +- .../Parsing/Header/HeaderFileReader.h | 3 + .../Parsing/Impl/DefinesStreamProxy.cpp | 301 ++++++++++++++++++ .../Parsing/Impl/DefinesStreamProxy.h | 43 +++ .../Parsing/ParsingException.cpp | 2 +- .../Parsing/Impl/DefinesStreamProxyTest.cpp | 297 +++++++++++++++++ 6 files changed, 668 insertions(+), 11 deletions(-) diff --git a/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.cpp b/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.cpp index 94caa504..2820f004 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.cpp +++ b/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.cpp @@ -2,7 +2,10 @@ #include + +#include "Parsing/ParsingException.h" #include "Parsing/Impl/CommentRemovingStreamProxy.h" +#include "Parsing/Impl/DefinesStreamProxy.h" #include "Parsing/Impl/IncludingStreamProxy.h" #include "Parsing/Impl/ParserFilesystemStream.h" @@ -17,7 +20,7 @@ bool HeaderFileReader::ReadHeaderFile(IDataRepository* repository) const std::cout << "Reading header file: " << m_filename << std::endl; ParserFilesystemStream stream(m_filename); - if(!stream.IsOpen()) + if (!stream.IsOpen()) { std::cout << "Could not open header file" << std::endl; return false; @@ -25,21 +28,31 @@ bool HeaderFileReader::ReadHeaderFile(IDataRepository* repository) const IParserLineStream* lineStream = &stream; - IncludingStreamProxy includeProxy(lineStream); - lineStream = &includeProxy; - CommentRemovingStreamProxy commentProxy(lineStream); lineStream = &commentProxy; + IncludingStreamProxy includeProxy(lineStream); + lineStream = &includeProxy; - while(true) + DefinesStreamProxy definesProxy(lineStream); + definesProxy.AddDefine(ZONE_CODE_GENERATOR_DEFINE_NAME, ZONE_CODE_GENERATOR_DEFINE_VALUE); + lineStream = &definesProxy; + + try { - auto line = lineStream->NextLine(); - - if (line.IsEof()) - break; + while (true) + { + auto line = lineStream->NextLine(); - std::cout << "Line " << line.m_filename << ":" << line.m_line_number << ": " << line.m_line << std::endl; + if (line.IsEof()) + break; + + std::cout << "Line " << line.m_filename << ":" << line.m_line_number << ": " << line.m_line << std::endl; + } + } + catch (const ParsingException& e) + { + std::cout << "Error: " << e.FullMessage() << std::endl; } return true; diff --git a/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.h b/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.h index bc5a09e6..fd5c50d6 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.h +++ b/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.h @@ -7,6 +7,9 @@ class HeaderFileReader { + static constexpr const char* ZONE_CODE_GENERATOR_DEFINE_NAME = "__zonecodegenerator"; + static constexpr const char* ZONE_CODE_GENERATOR_DEFINE_VALUE = "1"; + const ZoneCodeGeneratorArguments* m_args; std::string m_filename; diff --git a/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.cpp b/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.cpp index e69de29b..0b75bd68 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.cpp +++ b/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.cpp @@ -0,0 +1,301 @@ +#include "DefinesStreamProxy.h" + +#include +#include + +#include "Parsing/ParsingException.h" + +DefinesStreamProxy::DefinesStreamProxy(IParserLineStream* stream) + : m_stream(stream), + m_ignore_depth(0) +{ +} + +bool DefinesStreamProxy::FindDirective(const ParserLine& line, unsigned& directivePosition) +{ + directivePosition = 0; + for (; directivePosition < line.m_line.size(); directivePosition++) + { + const auto c = line.m_line[directivePosition]; + + if (isspace(c)) + continue; + + return c == '#'; + } + + return false; +} + + +bool DefinesStreamProxy::MatchDefineDirective(const ParserLine& line, const unsigned directivePosition) +{ + constexpr auto directiveLength = std::char_traits::length(DEFINE_DIRECTIVE); + if (line.m_line.compare(directivePosition + 1, directiveLength, DEFINE_DIRECTIVE) != 0) + return false; + + const auto nameStartPos = directivePosition + directiveLength + 1; + auto nameEndPos = nameStartPos; + for (; nameEndPos < line.m_line.size(); nameEndPos++) + { + if (isspace(line.m_line[nameEndPos])) + break; + } + + if (nameStartPos == nameEndPos) + throw ParsingException(TokenPos(line.m_filename, line.m_line_number, static_cast(nameStartPos + 1)), "Defines need a name."); + + const auto name = line.m_line.substr(nameStartPos, nameEndPos - nameStartPos); + std::string value; + if (nameEndPos < line.m_line.size()) + value = line.m_line.substr(nameEndPos + 1); + m_defines[name] = value; + + return true; +} + +bool DefinesStreamProxy::MatchUndefDirective(const ParserLine& line, const unsigned directivePosition) +{ + constexpr auto directiveLength = std::char_traits::length(UNDEF_DIRECTIVE); + if (line.m_line.compare(directivePosition + 1, directiveLength, UNDEF_DIRECTIVE) != 0) + return false; + + const auto nameStartPos = directivePosition + directiveLength + 1; + auto nameEndPos = nameStartPos; + for (; nameEndPos < line.m_line.size(); nameEndPos++) + { + if (isspace(line.m_line[nameEndPos])) + break; + } + + if (nameStartPos == nameEndPos) + throw ParsingException(TokenPos(line.m_filename, line.m_line_number, static_cast(nameStartPos + 1)), "Cannot undef without a name."); + + const auto name = line.m_line.substr(nameStartPos, nameEndPos - nameStartPos); + const auto entry = m_defines.find(name); + + if (entry != m_defines.end()) + m_defines.erase(entry); + + return true; +} + +bool DefinesStreamProxy::MatchIfdefDirective(const ParserLine& line, const unsigned directivePosition) +{ + constexpr auto directiveLengthIfdef = std::char_traits::length(IFDEF_DIRECTIVE); + constexpr auto directiveLengthIfndef = std::char_traits::length(IFNDEF_DIRECTIVE); + + auto len = directiveLengthIfdef; + auto reverse = false; + if (line.m_line.compare(directivePosition + 1, directiveLengthIfdef, IFDEF_DIRECTIVE) != 0) + { + if (line.m_line.compare(directivePosition + 1, directiveLengthIfndef, IFNDEF_DIRECTIVE) != 0) + return false; + len = directiveLengthIfndef; + reverse = true; + } + + if (!m_modes.empty() && !m_modes.top()) + { + m_ignore_depth++; + return true; + } + + const auto nameStartPos = directivePosition + len + 1; + auto nameEndPos = nameStartPos; + for (; nameEndPos < line.m_line.size(); nameEndPos++) + { + if (isspace(line.m_line[nameEndPos])) + break; + } + + if (nameStartPos == nameEndPos) + throw ParsingException(TokenPos(line.m_filename, line.m_line_number, static_cast(nameStartPos + 1)), "Cannot ifdef without a name."); + + const auto name = line.m_line.substr(nameStartPos, nameEndPos - nameStartPos); + const auto entry = m_defines.find(name); + + if (entry != m_defines.end()) + m_modes.push(!reverse); + else + m_modes.push(reverse); + + return true; +} + +bool DefinesStreamProxy::MatchElseDirective(const ParserLine& line, const unsigned directivePosition) +{ + constexpr auto directiveLength = std::char_traits::length(ELSE_DIRECTIVE); + if (line.m_line.compare(directivePosition + 1, directiveLength, ELSE_DIRECTIVE) != 0) + return false; + + if (m_ignore_depth > 0) + return true; + + if (!m_modes.empty()) + m_modes.top() = !m_modes.top(); + else + throw ParsingException(TokenPos(line.m_filename, line.m_line_number, static_cast(directivePosition + 1)), "Cannot use else without ifdef"); + + return true; +} + +bool DefinesStreamProxy::MatchEndifDirective(const ParserLine& line, const unsigned directivePosition) +{ + constexpr auto directiveLength = std::char_traits::length(ENDIF_DIRECTIVE); + if (line.m_line.compare(directivePosition + 1, directiveLength, ENDIF_DIRECTIVE) != 0) + return false; + + if (m_ignore_depth > 0) + { + m_ignore_depth--; + return true; + } + + if (!m_modes.empty()) + m_modes.pop(); + else + throw ParsingException(TokenPos(line.m_filename, line.m_line_number, static_cast(directivePosition + 1)), "Cannot use endif without ifdef"); + + return true; +} + +bool DefinesStreamProxy::MatchDirectives(const ParserLine& line) +{ + unsigned directivePos; + + if (!FindDirective(line, directivePos)) + return false; + + return MatchDefineDirective(line, directivePos) + || MatchUndefDirective(line, directivePos) + || MatchIfdefDirective(line, directivePos) + || MatchElseDirective(line, directivePos) + || MatchEndifDirective(line, directivePos); +} + +bool DefinesStreamProxy::FindDefineForWord(const ParserLine& line, const unsigned wordStart, const unsigned wordEnd, std::string& value) +{ + const std::string word(line.m_line, wordStart, wordEnd - wordStart); + const auto foundEntry = m_defines.find(word); + if(foundEntry != m_defines.end()) + { + value = foundEntry->second; + return true; + } + + return false; +} + +void DefinesStreamProxy::ExpandDefines(ParserLine& line) +{ + auto wordStart = 0u; + auto lastWordEnd = 0u; + auto inWord = false; + std::string value; + + std::ostringstream str; + auto usesDefines = false; + + for (auto i = 0u; i < line.m_line.size(); i++) + { + const auto c = line.m_line[i]; + if (!inWord) + { + if (isalpha(c) || c == '_') + { + wordStart = i; + inWord = true; + } + } + else + { + if (!isalnum(c) && c != '_') + { + if (FindDefineForWord(line, wordStart, i, value)) + { + if (!usesDefines) + { + str << std::string(line.m_line, 0, wordStart) << value; + usesDefines = true; + } + else + { + str << std::string(line.m_line, lastWordEnd, wordStart - lastWordEnd) << value; + } + lastWordEnd = i; + } + inWord = false; + } + } + } + + if (inWord) + { + if (FindDefineForWord(line, wordStart, line.m_line.size(), value)) + { + if (!usesDefines) + { + str << std::string(line.m_line, 0, wordStart) << value; + usesDefines = true; + } + else + { + str << std::string(line.m_line, lastWordEnd, wordStart - lastWordEnd) << value; + } + lastWordEnd = line.m_line.size(); + } + } + + if(usesDefines) + { + if (lastWordEnd < line.m_line.size()) + str << std::string(line.m_line, lastWordEnd, line.m_line.size() - lastWordEnd); + line.m_line = str.str(); + } +} + +void DefinesStreamProxy::AddDefine(const std::string& name, std::string value) +{ + m_defines[name] = std::move(value); +} + +void DefinesStreamProxy::Undefine(const std::string& name) +{ + const auto entry = m_defines.find(name); + + if (entry != m_defines.end()) + m_defines.erase(entry); +} + +ParserLine DefinesStreamProxy::NextLine() +{ + auto line = m_stream->NextLine(); + + if (MatchDirectives(line) + || !m_modes.empty() && !m_modes.top()) + { + line.m_line.clear(); + } + else + { + ExpandDefines(line); + } + + return line; +} + +bool DefinesStreamProxy::IncludeFile(const std::string& filename) +{ + return m_stream->IncludeFile(filename); +} + +bool DefinesStreamProxy::IsOpen() const +{ + return m_stream->IsOpen(); +} + +bool DefinesStreamProxy::Eof() const +{ + return m_stream->Eof(); +} diff --git a/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.h b/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.h index e69de29b..64b5cf03 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.h +++ b/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include "Parsing/IParserLineStream.h" + +class DefinesStreamProxy final : public IParserLineStream +{ + static constexpr const char* DEFINE_DIRECTIVE = "define "; + static constexpr const char* UNDEF_DIRECTIVE = "undef "; + static constexpr const char* IFDEF_DIRECTIVE = "ifdef "; + static constexpr const char* IFNDEF_DIRECTIVE = "ifndef "; + static constexpr const char* ELSE_DIRECTIVE = "else"; + static constexpr const char* ENDIF_DIRECTIVE = "endif"; + + std::unordered_map m_defines; + IParserLineStream* const m_stream; + std::stack m_modes; + unsigned m_ignore_depth; + + _NODISCARD static bool FindDirective(const ParserLine& line, unsigned& directivePosition); + _NODISCARD bool MatchDefineDirective(const ParserLine& line, unsigned directivePosition); + _NODISCARD bool MatchUndefDirective(const ParserLine& line, unsigned directivePosition); + _NODISCARD bool MatchIfdefDirective(const ParserLine& line, unsigned directivePosition); + _NODISCARD bool MatchElseDirective(const ParserLine& line, unsigned directivePosition); + _NODISCARD bool MatchEndifDirective(const ParserLine& line, unsigned directivePosition); + _NODISCARD bool MatchDirectives(const ParserLine& line); + + bool FindDefineForWord(const ParserLine& line, unsigned wordStart, unsigned wordEnd, std::string& value); + void ExpandDefines(ParserLine& line); + +public: + explicit DefinesStreamProxy(IParserLineStream* stream); + + void AddDefine(const std::string& name, std::string value); + void Undefine(const std::string& name); + + ParserLine NextLine() override; + bool IncludeFile(const std::string& filename) override; + _NODISCARD bool IsOpen() const override; + _NODISCARD bool Eof() const override; +}; diff --git a/src/ZoneCodeGeneratorLib/Parsing/ParsingException.cpp b/src/ZoneCodeGeneratorLib/Parsing/ParsingException.cpp index b21e5aae..ccad2836 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/ParsingException.cpp +++ b/src/ZoneCodeGeneratorLib/Parsing/ParsingException.cpp @@ -7,7 +7,7 @@ ParsingException::ParsingException(const TokenPos position, std::string message) m_message(std::move(message)) { std::ostringstream str; - str << m_pos.m_line << ':' << m_pos.m_column << ' ' << m_message; + str << position.m_filename << " L" << m_pos.m_line << ':' << m_pos.m_column << ' ' << m_message; m_full_message = str.str(); } diff --git a/test/ZoneCodeGeneratorLibTests/Parsing/Impl/DefinesStreamProxyTest.cpp b/test/ZoneCodeGeneratorLibTests/Parsing/Impl/DefinesStreamProxyTest.cpp index e69de29b..7943e786 100644 --- a/test/ZoneCodeGeneratorLibTests/Parsing/Impl/DefinesStreamProxyTest.cpp +++ b/test/ZoneCodeGeneratorLibTests/Parsing/Impl/DefinesStreamProxyTest.cpp @@ -0,0 +1,297 @@ +#include + +#include "Parsing/Impl/DefinesStreamProxy.h" +#include "Parsing/Mock/MockParserLineStream.h" + +namespace test::parsing +{ + void ExpectLine(IParserLineStream* stream, const int lineNumber, const std::string& value) + { + auto line = stream->NextLine(); + REQUIRE(line.m_line_number == lineNumber); + REQUIRE(line.m_line == value); + } + + TEST_CASE("DefinesStreamProxy: Ensure simple define and positive ifdef is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF", + "#ifdef ASDF", + "Hello World", + "#endif", + "Hello Galaxy" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, "Hello World"); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, "Hello Galaxy"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure simple define and negative ifdef is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF", + "#ifdef NONO", + "Hello World", + "#endif", + "Hello Galaxy" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, ""); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, "Hello Galaxy"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure simple define and positive ifndef is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF", + "#ifndef NONO", + "Hello World", + "#endif", + "Hello Galaxy" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, "Hello World"); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, "Hello Galaxy"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure simple define and negative ifndef is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF", + "#ifndef ASDF", + "Hello World", + "#endif", + "Hello Galaxy" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, ""); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, "Hello Galaxy"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure else is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF", + "#ifdef NONO", + "Hello World1", + "#else", + "Hello World2", + "#endif", + "Hello Galaxy" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, ""); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, "Hello World2"); + ExpectLine(&proxy, 6, ""); + ExpectLine(&proxy, 7, "Hello Galaxy"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure nested ifdef is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF", + "#ifdef ASDF", + "#ifdef NONO", + "Hello World1", + "#else", + "Hello World2", + "#endif", + "#else", + "#ifdef ASDF", + "Hello World3", + "#else", + "Hello World4", + "#endif", + "#endif", + "Hello Galaxy" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, ""); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, ""); + ExpectLine(&proxy, 6, "Hello World2"); + ExpectLine(&proxy, 7, ""); + ExpectLine(&proxy, 8, ""); + ExpectLine(&proxy, 9, ""); + ExpectLine(&proxy, 10, ""); + ExpectLine(&proxy, 11, ""); + ExpectLine(&proxy, 12, ""); + ExpectLine(&proxy, 13, ""); + ExpectLine(&proxy, 14, ""); + ExpectLine(&proxy, 15, "Hello Galaxy"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure undef is working", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF", + "#ifdef ASDF", + "Hello World", + "#endif", + "#undef ASDF", + "#ifdef ASDF", + "Hello World", + "#endif", + "Hello Galaxy" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, "Hello World"); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, ""); + ExpectLine(&proxy, 6, ""); + ExpectLine(&proxy, 7, ""); + ExpectLine(&proxy, 8, ""); + ExpectLine(&proxy, 9, "Hello Galaxy"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure undef does not undefine everything", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF", + "#ifdef ASDF", + "Hello World", + "#endif", + "#undef NONO", + "#ifdef ASDF", + "Hello World", + "#endif", + "Hello Galaxy" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, "Hello World"); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, ""); + ExpectLine(&proxy, 6, ""); + ExpectLine(&proxy, 7, "Hello World"); + ExpectLine(&proxy, 8, ""); + ExpectLine(&proxy, 9, "Hello Galaxy"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure simple defines are working", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF LOL", + "ASDF", + "A ASDF B", + "ASDF B", + "A ASDF" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "LOL"); + ExpectLine(&proxy, 3, "A LOL B"); + ExpectLine(&proxy, 4, "LOL B"); + ExpectLine(&proxy, 5, "A LOL"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure defines can be surrounded by symbols", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define ASDF LOL", + "!ASDF%" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "!LOL%"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure can use multiple defines in one line", "[parsing][parsingstream]") + { + const std::vector lines + { + "#define A Hello", + "#define B world", + "A my dear B!" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, "Hello my dear world!"); + + REQUIRE(proxy.Eof()); + } +}