diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b4a3ff8a5..08065bafa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -86,11 +86,18 @@ jobs: - name: Test working-directory: ${{ github.workspace }}/build/lib/Release_x86/tests run: | + $combinedExitCode = 0 ./ObjCommonTests + $combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE) ./ObjLoadingTests + $combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE) ./ParserTests + $combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE) ./ZoneCodeGeneratorLibTests + $combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE) ./ZoneCommonTests + $combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE) + exit $combinedExitCode - name: Upload artifacts uses: actions/upload-artifact@v4 with: diff --git a/src/Parser/Parsing/Impl/DefinesStreamProxy.cpp b/src/Parser/Parsing/Impl/DefinesStreamProxy.cpp index b54a5330a..0f955803e 100644 --- a/src/Parser/Parsing/Impl/DefinesStreamProxy.cpp +++ b/src/Parser/Parsing/Impl/DefinesStreamProxy.cpp @@ -123,7 +123,10 @@ DefinesStreamProxy::DefinesStreamProxy(IParserLineStream* stream, const bool ski : m_stream(stream), m_skip_directive_lines(skipDirectiveLines), m_ignore_depth(0), - m_in_define(false) + m_in_define(false), + m_parameter_state(ParameterState::NOT_IN_PARAMETERS), + m_current_macro(nullptr), + m_macro_parameter_state(ParameterState::NOT_IN_PARAMETERS) { } @@ -142,12 +145,19 @@ int DefinesStreamProxy::GetLineEndEscapePos(const ParserLine& line) return -1; } -void DefinesStreamProxy::ContinueDefine(const ParserLine& line) +void DefinesStreamProxy::ContinueDefine(const ParserLine& line, const unsigned currentPos) { const auto lineEndEscapePos = GetLineEndEscapePos(line); if (lineEndEscapePos < 0) { - m_current_define_value << line.m_line; + if (m_parameter_state != ParameterState::NOT_IN_PARAMETERS) + throw ParsingException(CreatePos(line, currentPos), "Unclosed macro parameters"); + + if (currentPos <= 0) + m_current_define_value << line.m_line; + else + m_current_define_value << line.m_line.substr(currentPos); + m_current_define.m_value = m_current_define_value.str(); m_current_define.IdentifyParameters(m_current_define_parameters); AddDefine(std::move(m_current_define)); @@ -159,49 +169,58 @@ void DefinesStreamProxy::ContinueDefine(const ParserLine& line) } else { - if (line.m_line.size() > static_cast(lineEndEscapePos)) - m_current_define_value << line.m_line.substr(0, static_cast(lineEndEscapePos)); + if (line.m_line.size() > static_cast(lineEndEscapePos) && currentPos < static_cast(lineEndEscapePos)) + m_current_define_value << line.m_line.substr(currentPos, static_cast(lineEndEscapePos) - currentPos); } } -std::vector DefinesStreamProxy::MatchDefineParameters(const ParserLine& line, unsigned& parameterPosition) +void DefinesStreamProxy::ContinueParameters(const ParserLine& line, unsigned& currentPos) { - if (line.m_line[parameterPosition] != '(') - return std::vector(); - - parameterPosition++; - std::vector parameters; - + const auto lineEndEscapePos = GetLineEndEscapePos(line); while (true) { - if (!SkipWhitespace(line, parameterPosition) || parameterPosition >= line.m_line.size()) - throw ParsingException(CreatePos(line, parameterPosition), "Invalid define parameter list"); + if (!SkipWhitespace(line, currentPos)) + throw ParsingException(CreatePos(line, currentPos), "Invalid define parameter list"); - const auto nameStartPos = parameterPosition; - if (!ExtractIdentifier(line, parameterPosition)) - throw ParsingException(CreatePos(line, parameterPosition), "Cannot extract name of parameter of define"); + if (lineEndEscapePos >= 0 && currentPos >= static_cast(lineEndEscapePos)) + return; - parameters.emplace_back(std::string(line.m_line, nameStartPos, parameterPosition - nameStartPos)); + if (currentPos >= line.m_line.size()) + throw ParsingException(CreatePos(line, currentPos), "Invalid define parameter list"); - if (!SkipWhitespace(line, parameterPosition)) - throw ParsingException(CreatePos(line, parameterPosition), "Unclosed define parameters"); - - if (parameterPosition >= line.m_line.size()) - throw ParsingException(CreatePos(line, parameterPosition), "Unclosed define parameters"); + if ((m_parameter_state == ParameterState::AFTER_OPEN || m_parameter_state == ParameterState::AFTER_PARAM) && line.m_line[currentPos] == ')') + { + currentPos++; + m_parameter_state = ParameterState::NOT_IN_PARAMETERS; + return; + } - if (line.m_line[parameterPosition] == ')') + if (m_parameter_state == ParameterState::AFTER_PARAM && line.m_line[currentPos] == ',') { - parameterPosition++; - break; + currentPos++; + m_parameter_state = ParameterState::AFTER_COMMA; + continue; } - if (line.m_line[parameterPosition] != ',') - throw ParsingException(CreatePos(line, parameterPosition), "Unknown symbol in define parameter list"); + const auto nameStartPos = currentPos; + if (!ExtractIdentifier(line, currentPos)) + throw ParsingException(CreatePos(line, currentPos), "Cannot extract name of parameter of define"); - parameterPosition++; + m_current_define_parameters.emplace_back(line.m_line, nameStartPos, currentPos - nameStartPos); + m_parameter_state = ParameterState::AFTER_PARAM; } +} + +void DefinesStreamProxy::MatchDefineParameters(const ParserLine& line, unsigned& currentPos) +{ + m_current_define_parameters = std::vector(); + if (line.m_line[currentPos] != '(') + return; + + m_parameter_state = ParameterState::AFTER_OPEN; + currentPos++; - return parameters; + ContinueParameters(line, currentPos); } bool DefinesStreamProxy::MatchDefineDirective(const ParserLine& line, const unsigned directiveStartPosition, const unsigned directiveEndPosition) @@ -223,29 +242,14 @@ bool DefinesStreamProxy::MatchDefineDirective(const ParserLine& line, const unsi const auto name = line.m_line.substr(nameStartPos, currentPos - nameStartPos); - auto parameters = MatchDefineParameters(line, currentPos); + MatchDefineParameters(line, currentPos); + SkipWhitespace(line, currentPos); - const auto lineEndEscapePos = GetLineEndEscapePos(line); - if (lineEndEscapePos < 0) - { - std::string value; - if (currentPos < line.m_line.size()) - value = line.m_line.substr(currentPos + 1); + m_in_define = true; + m_current_define = Define(name, std::string()); + m_current_define_value.str(std::string()); - Define define(name, value); - define.IdentifyParameters(parameters); - AddDefine(std::move(define)); - } - else - { - m_in_define = true; - m_current_define = Define(name, std::string()); - m_current_define_value.str(std::string()); - m_current_define_parameters = std::move(parameters); - - if (currentPos < line.m_line.size() && (currentPos + 1) < static_cast(lineEndEscapePos)) - m_current_define_value << line.m_line.substr(currentPos + 1, static_cast(lineEndEscapePos) - (currentPos + 1)); - } + ContinueDefine(line, currentPos); return true; } @@ -276,8 +280,7 @@ bool DefinesStreamProxy::MatchUndefDirective(const ParserLine& line, const unsig return true; } -std::unique_ptr - DefinesStreamProxy::ParseExpression(std::shared_ptr fileName, int lineNumber, std::string expressionString) const +std::unique_ptr DefinesStreamProxy::ParseExpression(std::shared_ptr fileName, int lineNumber, std::string expressionString) { ParserLine pseudoLine(std::move(fileName), lineNumber, std::move(expressionString)); ExpandDefinedExpressions(pseudoLine); @@ -451,7 +454,7 @@ bool DefinesStreamProxy::MatchEndifDirective(const ParserLine& line, const unsig return true; } -bool DefinesStreamProxy::MatchDirectives(const ParserLine& line) +bool DefinesStreamProxy::MatchDirectives(ParserLine& line) { unsigned directiveStartPos; unsigned directiveEndPos; @@ -472,9 +475,9 @@ bool DefinesStreamProxy::MatchDirectives(const ParserLine& line) || MatchEndifDirective(line, directiveStartPos, directiveEndPos); } -bool DefinesStreamProxy::FindDefineForWord(const ParserLine& line, const unsigned wordStart, const unsigned wordEnd, const Define*& value) const +bool DefinesStreamProxy::FindDefineForWord(const std::string& line, const unsigned wordStart, const unsigned wordEnd, const Define*& value) const { - const std::string word(line.m_line, wordStart, wordEnd - wordStart); + const std::string word(line, wordStart, wordEnd - wordStart); const auto foundEntry = m_defines.find(word); if (foundEntry != m_defines.end()) { @@ -485,71 +488,122 @@ bool DefinesStreamProxy::FindDefineForWord(const ParserLine& line, const unsigne return false; } -void DefinesStreamProxy::ExtractParametersFromDefineUsage(const ParserLine& line, - const unsigned parameterStart, - unsigned& parameterEnd, - std::vector& parameterValues) +void DefinesStreamProxy::ContinueMacroParameters(const ParserLine& line, unsigned& pos) { - if (line.m_line[parameterStart] != '(') - return; - - std::ostringstream currentValue; - auto pos = parameterStart + 1; - auto valueHasStarted = false; - auto parenthesisDepth = 0; - while (true) + const auto lineLength = line.m_line.size(); + while (m_macro_parameter_state != ParameterState::NOT_IN_PARAMETERS && pos < lineLength) { - if (pos >= line.m_line.size()) - throw ParsingException(CreatePos(line, pos), "Invalid use of define"); - const auto c = line.m_line[pos]; if (c == ',') { - if (parenthesisDepth > 0) + if (!m_macro_bracket_depth.empty()) { - valueHasStarted = true; - currentValue << c; + m_macro_parameter_state = ParameterState::AFTER_PARAM; + m_current_macro_parameter << c; } else { - parameterValues.emplace_back(currentValue.str()); - currentValue.clear(); - currentValue.str(std::string()); - valueHasStarted = false; + m_macro_parameters.emplace_back(m_current_macro_parameter.str()); + m_current_macro_parameter.clear(); + m_current_macro_parameter.str(std::string()); + m_macro_parameter_state = ParameterState::AFTER_COMMA; } } - else if (c == '(') + else if (c == '(' || c == '[' || c == '{') { - valueHasStarted = true; - parenthesisDepth++; - currentValue << c; + m_macro_parameter_state = ParameterState::AFTER_PARAM; + m_macro_bracket_depth.push(c); + m_current_macro_parameter << c; } else if (c == ')') { - if (parenthesisDepth > 0) + if (!m_macro_bracket_depth.empty()) + { + if (m_macro_bracket_depth.top() != '(') + throw ParsingException(CreatePos(line, pos), "Unbalanced brackets in macro parameters"); + + m_macro_bracket_depth.pop(); + m_macro_parameter_state = ParameterState::AFTER_PARAM; + m_current_macro_parameter << c; + } + else if (m_macro_parameter_state == ParameterState::AFTER_COMMA) { - valueHasStarted = true; - parenthesisDepth--; - currentValue << c; + throw ParsingException(CreatePos(line, pos), "Cannot close macro parameters after comma"); } else { - parameterValues.emplace_back(currentValue.str()); - parameterEnd = pos + 1; - break; + m_macro_parameters.emplace_back(m_current_macro_parameter.str()); + m_macro_parameter_state = ParameterState::NOT_IN_PARAMETERS; + } + } + else if (c == ']' || c == '}') + { + if (!m_macro_bracket_depth.empty()) + { + const auto otherBracket = c == ']' ? '[' : '{'; + if (m_macro_bracket_depth.top() != otherBracket) + throw ParsingException(CreatePos(line, pos), "Unbalanced brackets in macro parameters"); + m_macro_bracket_depth.pop(); } + + m_macro_parameter_state = ParameterState::AFTER_PARAM; + m_current_macro_parameter << c; } - else if (valueHasStarted || !isspace(c)) + else if (m_macro_parameter_state == ParameterState::AFTER_PARAM || !isspace(c)) { - valueHasStarted = true; - currentValue << c; + m_macro_parameter_state = ParameterState::AFTER_PARAM; + m_current_macro_parameter << c; } pos++; } } +void DefinesStreamProxy::ContinueMacro(ParserLine& line) +{ + auto pos = 0u; + ContinueMacroParameters(line, pos); + + if (m_macro_parameter_state == ParameterState::NOT_IN_PARAMETERS) + { + const auto defineValue = m_current_macro->Render(m_macro_parameters); + + if (pos < line.m_line.size()) + { + std::ostringstream ss; + ss << defineValue; + ss << std::string(line.m_line, pos, line.m_line.size() - pos); + line.m_line = ss.str(); + } + else + { + line.m_line = defineValue; + } + + ExpandDefines(line); + } + else + { + line.m_line = ""; + } +} + +void DefinesStreamProxy::ExtractParametersFromDefineUsage(const ParserLine& line, const unsigned parameterStart, unsigned& parameterEnd) +{ + if (line.m_line[parameterStart] != '(') + return; + + m_macro_parameter_state = ParameterState::AFTER_OPEN; + m_macro_parameters = std::vector(); + m_current_macro_parameter.clear(); + m_current_macro_parameter.str(std::string()); + m_macro_bracket_depth = std::stack(); + parameterEnd = parameterStart + 1; + + ContinueMacroParameters(line, parameterEnd); +} + bool DefinesStreamProxy::MatchDefinedExpression(const ParserLine& line, unsigned& pos, std::string& definitionName) { unsigned currentPos = pos; @@ -601,86 +655,102 @@ void DefinesStreamProxy::ExpandDefinedExpressions(ParserLine& line) const } } -void DefinesStreamProxy::ExpandDefines(ParserLine& line) const +void DefinesStreamProxy::ProcessDefine(const ParserLine& line, unsigned& pos, std::ostringstream& out) +{ + ExtractParametersFromDefineUsage(line, pos, pos); + + if (m_macro_parameter_state == ParameterState::NOT_IN_PARAMETERS) + { + const auto defineValue = m_current_macro->Render(m_macro_parameters); + out << defineValue; + } +} + +bool DefinesStreamProxy::FindNextDefine(const std::string& line, unsigned& pos, unsigned& defineStart, const DefinesStreamProxy::Define*& define) +{ + const auto lineSize = line.size(); + auto wordStart = 0u; + auto lastWordEnd = 0u; + auto inWord = false; + + for (; pos < lineSize; pos++) + { + const auto c = line[pos]; + if (!inWord) + { + if (isalpha(c) || c == '_') + { + wordStart = pos; + inWord = true; + } + } + else + { + if (!isalnum(c) && c != '_') + { + if (FindDefineForWord(line, wordStart, pos, define)) + { + defineStart = wordStart; + return true; + } + + inWord = false; + } + } + } + + if (inWord) + { + if (FindDefineForWord(line, wordStart, pos, define)) + { + defineStart = wordStart; + return true; + } + } + + return false; +} + +void DefinesStreamProxy::ExpandDefines(ParserLine& line) { - bool usesDefines; auto defineIterations = 0u; + bool usesDefines; do { if (defineIterations > MAX_DEFINE_ITERATIONS) + { throw ParsingException(CreatePos(line, 1), "Potential define loop? Exceeded max define iterations of " + std::to_string(MAX_DEFINE_ITERATIONS) + " iterations."); + } usesDefines = false; - - auto wordStart = 0u; - auto lastWordEnd = 0u; - auto inWord = false; - const Define* value; + auto pos = 0u; + auto defineStart = 0u; + auto lastDefineEnd = 0u; std::ostringstream str; - for (auto i = 0u; i < line.m_line.size(); i++) + while (FindNextDefine(line.m_line, pos, defineStart, m_current_macro)) { - const auto c = line.m_line[i]; - if (!inWord) + if (!usesDefines) { - if (isalpha(c) || c == '_') - { - wordStart = i; - inWord = true; - } + usesDefines = true; + str << std::string(line.m_line, 0, defineStart); } else { - if (!isalnum(c) && c != '_') - { - if (FindDefineForWord(line, wordStart, i, value)) - { - std::vector parameterValues; - ExtractParametersFromDefineUsage(line, i, i, parameterValues); - const auto defineValue = value->Render(parameterValues); - - if (!usesDefines) - { - str << std::string(line.m_line, 0, wordStart) << defineValue; - usesDefines = true; - } - else - { - str << std::string(line.m_line, lastWordEnd, wordStart - lastWordEnd) << defineValue; - } - lastWordEnd = i; - } - inWord = false; - } + str << std::string(line.m_line, lastDefineEnd, defineStart - (lastDefineEnd)); } - } - if (inWord) - { - if (FindDefineForWord(line, wordStart, line.m_line.size(), value)) - { - const std::vector parameterValues; - const auto defineValue = value->Render(parameterValues); + ProcessDefine(line, pos, str); - if (!usesDefines) - { - str << std::string(line.m_line, 0, wordStart) << defineValue; - usesDefines = true; - } - else - { - str << std::string(line.m_line, lastWordEnd, wordStart - lastWordEnd) << defineValue; - } - lastWordEnd = line.m_line.size(); - } + lastDefineEnd = pos; } if (usesDefines) { - if (lastWordEnd < line.m_line.size()) - str << std::string(line.m_line, lastWordEnd, line.m_line.size() - lastWordEnd); + if (lastDefineEnd < line.m_line.size()) + str << std::string(line.m_line, lastDefineEnd, line.m_line.size() - lastDefineEnd); line.m_line = str.str(); } @@ -709,7 +779,15 @@ ParserLine DefinesStreamProxy::NextLine() { if (m_in_define) { - ContinueDefine(line); + unsigned currentPos = 0u; + + if (m_parameter_state != ParameterState::NOT_IN_PARAMETERS) + { + ContinueParameters(line, currentPos); + SkipWhitespace(line, currentPos); + } + + ContinueDefine(line, currentPos); if (!m_skip_directive_lines) { line.m_line = std::string(); @@ -718,6 +796,11 @@ ParserLine DefinesStreamProxy::NextLine() line = m_stream->NextLine(); } + else if (m_macro_parameter_state != ParameterState::NOT_IN_PARAMETERS) + { + ContinueMacro(line); + return line; + } else if (MatchDirectives(line) || !m_modes.empty() && m_modes.top() != BlockMode::IN_BLOCK) { if (!m_skip_directive_lines) diff --git a/src/Parser/Parsing/Impl/DefinesStreamProxy.h b/src/Parser/Parsing/Impl/DefinesStreamProxy.h index 8b216981a..cedd48c01 100644 --- a/src/Parser/Parsing/Impl/DefinesStreamProxy.h +++ b/src/Parser/Parsing/Impl/DefinesStreamProxy.h @@ -47,13 +47,21 @@ class DefinesStreamProxy final : public AbstractDirectiveStreamProxy }; private: - enum class BlockMode + enum class BlockMode : uint8_t { NOT_IN_BLOCK, IN_BLOCK, BLOCK_BLOCKED }; + enum class ParameterState : uint8_t + { + NOT_IN_PARAMETERS, + AFTER_OPEN, + AFTER_PARAM, + AFTER_COMMA + }; + IParserLineStream* const m_stream; const bool m_skip_directive_lines; std::map m_defines; @@ -61,13 +69,21 @@ class DefinesStreamProxy final : public AbstractDirectiveStreamProxy unsigned m_ignore_depth; bool m_in_define; + ParameterState m_parameter_state; Define m_current_define; std::ostringstream m_current_define_value; std::vector m_current_define_parameters; + const Define* m_current_macro; + ParameterState m_macro_parameter_state; + std::vector m_macro_parameters; + std::ostringstream m_current_macro_parameter; + std::stack m_macro_bracket_depth; + static int GetLineEndEscapePos(const ParserLine& line); - static std::vector MatchDefineParameters(const ParserLine& line, unsigned& parameterPosition); - void ContinueDefine(const ParserLine& line); + void MatchDefineParameters(const ParserLine& line, unsigned& currentPos); + void ContinueDefine(const ParserLine& line, unsigned currentPos); + void ContinueParameters(const ParserLine& line, unsigned& currentPos); _NODISCARD bool MatchDefineDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition); _NODISCARD bool MatchUndefDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition); _NODISCARD bool MatchIfDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition); @@ -75,24 +91,28 @@ class DefinesStreamProxy final : public AbstractDirectiveStreamProxy _NODISCARD bool MatchIfdefDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition); _NODISCARD bool MatchElseDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition); _NODISCARD bool MatchEndifDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition); - _NODISCARD bool MatchDirectives(const ParserLine& line); + _NODISCARD bool MatchDirectives(ParserLine& line); - static void - ExtractParametersFromDefineUsage(const ParserLine& line, unsigned parameterStart, unsigned& parameterEnd, std::vector& parameterValues); - bool FindDefineForWord(const ParserLine& line, unsigned wordStart, unsigned wordEnd, const Define*& value) const; + void ExtractParametersFromDefineUsage(const ParserLine& line, unsigned parameterStart, unsigned& parameterEnd); + bool FindDefineForWord(const std::string& line, unsigned wordStart, unsigned wordEnd, const Define*& value) const; static bool MatchDefinedExpression(const ParserLine& line, unsigned& pos, std::string& definitionName); void ExpandDefinedExpressions(ParserLine& line) const; + void ContinueMacroParameters(const ParserLine& line, unsigned& pos); + void ContinueMacro(ParserLine& line); + void ProcessDefine(const ParserLine& line, unsigned& pos, std::ostringstream& out); + bool FindNextDefine(const std::string& line, unsigned& pos, unsigned& defineStart, const DefinesStreamProxy::Define*& define); + public: explicit DefinesStreamProxy(IParserLineStream* stream, bool skipDirectiveLines = false); void AddDefine(Define define); void Undefine(const std::string& name); - void ExpandDefines(ParserLine& line) const; + void ExpandDefines(ParserLine& line); - _NODISCARD std::unique_ptr ParseExpression(std::shared_ptr fileName, int lineNumber, std::string expressionString) const; + _NODISCARD std::unique_ptr ParseExpression(std::shared_ptr fileName, int lineNumber, std::string expressionString); ParserLine NextLine() override; bool IncludeFile(const std::string& filename) override; diff --git a/src/Parser/Parsing/ParsingException.h b/src/Parser/Parsing/ParsingException.h index e036f8732..34876ff14 100644 --- a/src/Parser/Parsing/ParsingException.h +++ b/src/Parser/Parsing/ParsingException.h @@ -6,7 +6,7 @@ #include #include -class ParsingException final : std::exception +class ParsingException final : public std::exception { TokenPos m_pos; std::string m_message; diff --git a/test/ParserTests/Parsing/Impl/DefinesStreamProxyTests.cpp b/test/ParserTests/Parsing/Impl/DefinesStreamProxyTests.cpp index 335c8c6c3..6ca696363 100644 --- a/test/ParserTests/Parsing/Impl/DefinesStreamProxyTests.cpp +++ b/test/ParserTests/Parsing/Impl/DefinesStreamProxyTests.cpp @@ -1,8 +1,14 @@ #include "Parsing/Impl/DefinesStreamProxy.h" #include "Parsing/Mock/MockParserLineStream.h" +#include "Parsing/ParsingException.h" #include #include +#include +#include +#include + +using namespace Catch::Matchers; namespace test::parsing::impl::defines_stream_proxy { @@ -13,9 +19,21 @@ namespace test::parsing::impl::defines_stream_proxy REQUIRE(line.m_line == value); } + void ExpectErrorInLine(IParserLineStream* stream, const int lineNumber, const int columnNumber) + { + REQUIRE_THROWS_MATCHES( + stream->NextLine(), ParsingException, MessageMatches(ContainsSubstring("L" + std::to_string(lineNumber) + ":" + std::to_string(columnNumber)))); + } + 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"}; + const std::vector lines{ + "#define ASDF", + "#ifdef ASDF", + "Hello World", + "#endif", + "Hello Galaxy", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -31,7 +49,13 @@ namespace test::parsing::impl::defines_stream_proxy 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"}; + const std::vector lines{ + "#define ASDF", + "#ifdef NONO", + "Hello World", + "#endif", + "Hello Galaxy", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -47,7 +71,13 @@ namespace test::parsing::impl::defines_stream_proxy 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"}; + const std::vector lines{ + "#define ASDF", + "#ifndef NONO", + "Hello World", + "#endif", + "Hello Galaxy", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -63,7 +93,13 @@ namespace test::parsing::impl::defines_stream_proxy 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"}; + const std::vector lines{ + "#define ASDF", + "#ifndef ASDF", + "Hello World", + "#endif", + "Hello Galaxy", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -79,7 +115,15 @@ namespace test::parsing::impl::defines_stream_proxy 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"}; + const std::vector lines{ + "#define ASDF", + "#ifdef NONO", + "Hello World1", + "#else", + "Hello World2", + "#endif", + "Hello Galaxy", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -97,21 +141,23 @@ namespace test::parsing::impl::defines_stream_proxy 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"}; + 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); @@ -138,7 +184,16 @@ namespace test::parsing::impl::defines_stream_proxy 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"}; + "#define ASDF", + "#ifdef ASDF", + "Hello World", + "#endif", + "#undef ASDF", + "#ifdef ASDF", + "Hello World", + "#endif", + "Hello Galaxy", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -159,7 +214,16 @@ namespace test::parsing::impl::defines_stream_proxy 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"}; + "#define ASDF", + "#ifdef ASDF", + "Hello World", + "#endif", + "#undef NONO", + "#ifdef ASDF", + "Hello World", + "#endif", + "Hello Galaxy", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -179,7 +243,13 @@ namespace test::parsing::impl::defines_stream_proxy 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"}; + const std::vector lines{ + "#define ASDF LOL", + "ASDF", + "A ASDF B", + "ASDF B", + "A ASDF", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -195,7 +265,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure defines can be surrounded by symbols", "[parsing][parsingstream]") { - const std::vector lines{"#define ASDF LOL", "!ASDF%"}; + const std::vector lines{ + "#define ASDF LOL", + "!ASDF%", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -208,7 +281,11 @@ namespace test::parsing::impl::defines_stream_proxy 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!"}; + const std::vector lines{ + "#define A Hello", + "#define B world", + "A my dear B!", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -222,7 +299,12 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure defines in disabled block are ignored", "[parsing][parsingstream]") { - const std::vector lines{"#ifdef LOLO", "#define hello world", "#endif", "hello"}; + const std::vector lines{ + "#ifdef LOLO", + "#define hello world", + "#endif", + "hello", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -237,7 +319,13 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure undefs in disabled block are ignored", "[parsing][parsingstream]") { - const std::vector lines{"#define hello world", "#ifdef LOLO", "#undef hello", "#endif", "hello"}; + const std::vector lines{ + "#define hello world", + "#ifdef LOLO", + "#undef hello", + "#endif", + "hello", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -253,7 +341,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can define name with underscores and digits", "[parsing][parsingstream]") { - const std::vector lines{"#define __int16 short", "unsigned __int16 value;"}; + const std::vector lines{ + "#define __int16 short", + "unsigned __int16 value;", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -288,7 +379,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can add define with parameters", "[parsing][parsingstream]") { - const std::vector lines{"#define test(x) alignas(x)", "struct test(1337) test_struct"}; + const std::vector lines{ + "#define test(x) alignas(x)", + "struct test(1337) test_struct", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -301,7 +395,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can use parameter multiple times", "[parsing][parsingstream]") { - const std::vector lines{"#define test(x) x|x|x|x", "struct test(1337) test_struct"}; + const std::vector lines{ + "#define test(x) x|x|x|x", + "struct test(1337) test_struct", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -314,7 +411,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can use parameterized define in between symbols", "[parsing][parsingstream]") { - const std::vector lines{"#define test(x) x|x|x|x", "%test(5)%"}; + const std::vector lines{ + "#define test(x) x|x|x|x", + "%test(5)%", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -327,7 +427,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can define multiple parameters", "[parsing][parsingstream]") { - const std::vector lines{"#define test(a1, a2, a3) a1 + a2 = a3", "make calc test(1, 2, 3);"}; + const std::vector lines{ + "#define test(a1, a2, a3) a1 + a2 = a3", + "make calc test(1, 2, 3);", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -340,7 +443,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can define multiple parameters without spacing", "[parsing][parsingstream]") { - const std::vector lines{"#define test(a1,a2,a3) a1+a2=a3", "make calc test(1,2,3);"}; + const std::vector lines{ + "#define test(a1,a2,a3) a1+a2=a3", + "make calc test(1,2,3);", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -353,7 +459,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can define parameters with underscore", "[parsing][parsingstream]") { - const std::vector lines{"#define test(test_parameter) this is test_parameter", "Apparently test(a very cool text);"}; + const std::vector lines{ + "#define test(test_parameter) this is test_parameter", + "Apparently test(a very cool text);", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -558,7 +667,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can use parenthesis in parameters values", "[parsing][parsingstream]") { - const std::vector lines{"#define someStuff(param1) Hello param1 World", "someStuff(A sentence with (parenthesis) and stuff)"}; + const std::vector lines{ + "#define someStuff(param1) Hello param1 World", + "someStuff(A sentence with (parenthesis) and stuff)", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -571,8 +683,10 @@ namespace test::parsing::impl::defines_stream_proxy TEST_CASE("DefinesStreamProxy: Ensure can use comma in parenthesis in parameters values", "[parsing][parsingstream]") { - const std::vector lines{"#define someStuff(param1) Hello param1 World", - "someStuff(A sentence with (parenthesis and a , character) and stuff)"}; + const std::vector lines{ + "#define someStuff(param1) Hello param1 World", + "someStuff(A sentence with (parenthesis and a , character) and stuff)", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -583,9 +697,83 @@ namespace test::parsing::impl::defines_stream_proxy REQUIRE(proxy.Eof()); } + TEST_CASE("DefinesStreamProxy: Ensure can use comma in square brackets in parameters values", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define someStuff(param1) Hello param1 World", + "someStuff(A sentence with [brackets and a , character] and stuff)", + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "Hello A sentence with [brackets and a , character] and stuff World"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure can use comma in curly braces in parameters values", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define someStuff(param1) Hello param1 World", + "someStuff(A sentence with {braces and a , character} and stuff)", + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "Hello A sentence with {braces and a , character} and stuff World"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure throws error on unclosed parenthesis in params", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define someStuff(param1) Hello param1 World", + "someStuff(A sentence with [brackets and a , character and stuff)", + "someStuff(A sentence with {braces and a , character and stuff)", + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectErrorInLine(&proxy, 2, 64); + ExpectErrorInLine(&proxy, 3, 62); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Ensure throws error on parenthesis in params closed with wrong equivalent", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define someStuff(param1) Hello param1 World", + "someStuff(A sentence with (parenthesis and a , character] and stuff)", + "someStuff(A sentence with [brackets and a , character} and stuff)", + "someStuff(A sentence with {braces and a , character) and stuff)", + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectErrorInLine(&proxy, 2, 57); + ExpectErrorInLine(&proxy, 3, 54); + ExpectErrorInLine(&proxy, 4, 52); + + REQUIRE(proxy.Eof()); + } + TEST_CASE("DefinesStreamProxy: Ensure defines can go over multiple lines", "[parsing][parsingstream]") { - const std::vector lines{"#define someStuff(param1) Hello param1 World \\", "and hello universe", "someStuff(lovely)"}; + const std::vector lines{ + "#define someStuff(param1) Hello param1 World \\", + "and hello universe", + "someStuff(lovely)", + }; MockParserLineStream mockStream(lines); DefinesStreamProxy proxy(&mockStream); @@ -596,4 +784,120 @@ namespace test::parsing::impl::defines_stream_proxy REQUIRE(proxy.Eof()); } + + TEST_CASE("DefinesStreamProxy: Macro definition can span multiple lines when used with backslash", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define testMacro( \\", + " a, \\", + " b, \\", + " c) a + b - c", + "testMacro(1, 2, 3)", + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, ""); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, "1 + 2 - 3"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Macro definition that has unclosed parameters throws an error", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define testMacro(", + " a,", + " b,", + " c) a + b - c", + "testMacro(1, 2, 3)", + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectErrorInLine(&proxy, 1, 19); + } + + TEST_CASE("DefinesStreamProxy: Macro usages can span multiple lines if they have args", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define testMacro(a, b, c) Hello a, this is b. Lets meet at c!", + "testMacro(", + "Peter,", + "Anna,", + "the cinema", + ")", + }; + + 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 Peter, this is Anna. Lets meet at the cinema!"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Can use second macro after multi-line macro", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define LOL HAHA", + "#define testMacro(a, b) a likes b", + "testMacro(", + "Peter,", + "Anna", + ") LOL funny", + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, ""); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, ""); + ExpectLine(&proxy, 6, "Peter likes Anna HAHA funny"); + + REQUIRE(proxy.Eof()); + } + + TEST_CASE("DefinesStreamProxy: Can use second multi-line macro after multi-line macro", "[parsing][parsingstream]") + { + const std::vector lines{ + "#define LOL HAHA", + "#define testMacro(a, b) a likes b", + "testMacro(", + "Peter,", + "Anna", + ") and testMacro(", + "Anna,", + "Peter", + ")", + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, ""); + ExpectLine(&proxy, 3, ""); + ExpectLine(&proxy, 4, ""); + ExpectLine(&proxy, 5, ""); + ExpectLine(&proxy, 6, "Peter likes Anna and "); + ExpectLine(&proxy, 7, ""); + ExpectLine(&proxy, 8, ""); + ExpectLine(&proxy, 9, "Anna likes Peter"); + + REQUIRE(proxy.Eof()); + } } // namespace test::parsing::impl::defines_stream_proxy