From 5f3aa2a460294f7e3ef815231eda6a3bc744687f Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 4 Mar 2021 11:37:06 +0100 Subject: [PATCH] Implement defines with parameters to ZoneCodeGenerator --- .../Parsing/Commands/CommandsFileReader.cpp | 2 +- .../Parsing/Header/HeaderFileReader.cpp | 2 +- .../Parsing/Impl/DefinesStreamProxy.cpp | 227 ++++++++++++++++-- .../Parsing/Impl/DefinesStreamProxy.h | 35 ++- .../Parsing/Impl/DefinesStreamProxyTests.cpp | 115 +++++++++ 5 files changed, 360 insertions(+), 21 deletions(-) diff --git a/src/ZoneCodeGeneratorLib/Parsing/Commands/CommandsFileReader.cpp b/src/ZoneCodeGeneratorLib/Parsing/Commands/CommandsFileReader.cpp index 8b44e176..c9bd679d 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Commands/CommandsFileReader.cpp +++ b/src/ZoneCodeGeneratorLib/Parsing/Commands/CommandsFileReader.cpp @@ -43,7 +43,7 @@ void CommandsFileReader::SetupStreamProxies() auto commentProxy = std::make_unique(m_stream); auto includeProxy = std::make_unique(commentProxy.get()); auto definesProxy = std::make_unique(includeProxy.get()); - definesProxy->AddDefine(ZONE_CODE_GENERATOR_DEFINE_NAME, ZONE_CODE_GENERATOR_DEFINE_VALUE); + definesProxy->AddDefine(DefinesStreamProxy::Define(ZONE_CODE_GENERATOR_DEFINE_NAME, ZONE_CODE_GENERATOR_DEFINE_VALUE)); m_stream = definesProxy.get(); diff --git a/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.cpp b/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.cpp index c724fc93..83871165 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.cpp +++ b/src/ZoneCodeGeneratorLib/Parsing/Header/HeaderFileReader.cpp @@ -44,7 +44,7 @@ void HeaderFileReader::SetupStreamProxies() auto includeProxy = std::make_unique(commentProxy.get()); auto packProxy = std::make_unique(includeProxy.get()); auto definesProxy = std::make_unique(packProxy.get()); - definesProxy->AddDefine(ZONE_CODE_GENERATOR_DEFINE_NAME, ZONE_CODE_GENERATOR_DEFINE_VALUE); + definesProxy->AddDefine(DefinesStreamProxy::Define(ZONE_CODE_GENERATOR_DEFINE_NAME, ZONE_CODE_GENERATOR_DEFINE_VALUE)); m_pack_value_supplier = packProxy.get(); m_stream = definesProxy.get(); diff --git a/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.cpp b/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.cpp index 1c179210..65d6e0ab 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.cpp +++ b/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.cpp @@ -5,12 +5,158 @@ #include "Parsing/ParsingException.h" +DefinesStreamProxy::DefineParameterPosition::DefineParameterPosition() + : m_parameter_index(0u), + m_parameter_position(0u) +{ +} + +DefinesStreamProxy::DefineParameterPosition::DefineParameterPosition(const unsigned index, const unsigned position) + : m_parameter_index(index), + m_parameter_position(position) +{ +} + +DefinesStreamProxy::Define::Define() += default; + +DefinesStreamProxy::Define::Define(std::string name, std::string value) + : m_name(std::move(name)), + m_value(std::move(value)) +{ +} + +std::string DefinesStreamProxy::Define::Render(std::vector& parameterValues) +{ + if (parameterValues.empty() || m_parameter_positions.empty()) + return m_value; + + std::ostringstream str; + auto lastPos = 0u; + for (const auto& parameterPosition : m_parameter_positions) + { + if (lastPos < parameterPosition.m_parameter_position) + str << std::string(m_value, lastPos, parameterPosition.m_parameter_position - lastPos); + + if (parameterPosition.m_parameter_index < parameterValues.size()) + { + str << parameterValues[parameterPosition.m_parameter_index]; + } + + lastPos = parameterPosition.m_parameter_position; + } + + if (lastPos < m_value.size()) + str << std::string(m_value, lastPos, m_value.size() - lastPos); + + return str.str(); +} + +void DefinesStreamProxy::Define::IdentifyParameters(std::vector& parameterNames) +{ + if (parameterNames.empty()) + return; + + auto inWord = false; + auto wordStart = 0u; + for (auto i = 0u; i < m_value.size(); i++) + { + const auto c = m_value[i]; + if (!isalnum(c)) + { + if (inWord) + { + const std::string word(m_value, wordStart, i - wordStart); + + auto parameterIndex = 0u; + for (; parameterIndex < parameterNames.size(); parameterIndex++) + { + if (word == parameterNames[parameterIndex]) + break; + } + + if (parameterIndex < parameterNames.size()) + { + m_value.erase(wordStart, i - wordStart); + m_parameter_positions.emplace_back(parameterIndex, wordStart); + i = wordStart; + } + + inWord = false; + } + } + else + { + if (!inWord && (isalpha(c) || c == '_')) + { + inWord = true; + wordStart = i; + } + } + } + + if (inWord) + { + const std::string word(m_value, wordStart, m_value.size() - wordStart); + + auto parameterIndex = 0u; + for (; parameterIndex < parameterNames.size(); parameterIndex++) + { + if (word == parameterNames[parameterIndex]) + break; + } + + if (parameterIndex < parameterNames.size()) + { + m_value.erase(wordStart, m_value.size() - wordStart); + m_parameter_positions.emplace_back(parameterIndex, wordStart); + } + } +} + DefinesStreamProxy::DefinesStreamProxy(IParserLineStream* stream) : m_stream(stream), m_ignore_depth(0) { } +std::vector DefinesStreamProxy::MatchDefineParameters(const ParserLine& line, unsigned& parameterPosition) +{ + if (line.m_line[parameterPosition] != '(') + return std::vector(); + + parameterPosition++; + std::vector parameters; + + while (true) + { + if (!SkipWhitespace(line, parameterPosition) || parameterPosition >= line.m_line.size()) + throw ParsingException(CreatePos(line, parameterPosition), "Invalid define parameter list"); + + const auto nameStartPos = parameterPosition; + if (!ExtractIdentifier(line, parameterPosition)) + throw ParsingException(CreatePos(line, parameterPosition), "Cannot extract name of parameter of define"); + + parameters.emplace_back(std::string(line.m_line, nameStartPos, parameterPosition - nameStartPos)); + + if (parameterPosition >= line.m_line.size()) + throw ParsingException(CreatePos(line, parameterPosition), "Unclosed define parameters"); + + if (line.m_line[parameterPosition] == ')') + { + parameterPosition++; + break; + } + + if (line.m_line[parameterPosition] != ',') + throw ParsingException(CreatePos(line, parameterPosition), "Unknown symbol in define parameter list"); + + parameterPosition++; + } + + return parameters; +} + bool DefinesStreamProxy::MatchDefineDirective(const ParserLine& line, unsigned directivePosition) { if (!MatchString(line, directivePosition, DEFINE_DIRECTIVE, std::char_traits::length(DEFINE_DIRECTIVE))) @@ -21,10 +167,16 @@ bool DefinesStreamProxy::MatchDefineDirective(const ParserLine& line, unsigned d throw ParsingException(CreatePos(line, directivePosition), "Cannot ifdef without a name."); const auto name = line.m_line.substr(nameStartPos, directivePosition - nameStartPos); + + auto parameters = MatchDefineParameters(line, directivePosition); + std::string value; if (directivePosition < line.m_line.size()) value = line.m_line.substr(directivePosition + 1); - m_defines[name] = value; + + Define define(name, value); + define.IdentifyParameters(parameters); + AddDefine(std::move(define)); return true; } @@ -53,7 +205,7 @@ bool DefinesStreamProxy::MatchUndefDirective(const ParserLine& line, unsigned di bool DefinesStreamProxy::MatchIfdefDirective(const ParserLine& line, unsigned directivePosition) { auto reverse = false; - if(!MatchString(line, directivePosition, IFDEF_DIRECTIVE, std::char_traits::length(IFDEF_DIRECTIVE))) + if (!MatchString(line, directivePosition, IFDEF_DIRECTIVE, std::char_traits::length(IFDEF_DIRECTIVE))) { if (!MatchString(line, directivePosition, IFNDEF_DIRECTIVE, std::char_traits::length(IFNDEF_DIRECTIVE))) return false; @@ -71,7 +223,7 @@ bool DefinesStreamProxy::MatchIfdefDirective(const ParserLine& line, unsigned di throw ParsingException(CreatePos(line, directivePosition), "Cannot ifdef without a name."); const auto nameStartPos = directivePosition; - if(!ExtractIdentifier(line, directivePosition)) + if (!ExtractIdentifier(line, directivePosition)) throw ParsingException(CreatePos(line, directivePosition), "Cannot ifdef without a name."); const auto name = line.m_line.substr(nameStartPos, directivePosition - nameStartPos); @@ -129,7 +281,7 @@ bool DefinesStreamProxy::MatchDirectives(const ParserLine& line) directivePos++; - if(m_modes.empty() || m_modes.top() == true) + if (m_modes.empty() || m_modes.top() == true) { if (MatchDefineDirective(line, directivePos) || MatchUndefDirective(line, directivePos)) @@ -141,25 +293,63 @@ bool DefinesStreamProxy::MatchDirectives(const ParserLine& line) || MatchEndifDirective(line, directivePos); } -bool DefinesStreamProxy::FindDefineForWord(const ParserLine& line, const unsigned wordStart, const unsigned wordEnd, std::string& value) +bool DefinesStreamProxy::FindDefineForWord(const ParserLine& line, const unsigned wordStart, const unsigned wordEnd, Define*& value) { const std::string word(line.m_line, wordStart, wordEnd - wordStart); const auto foundEntry = m_defines.find(word); - if(foundEntry != m_defines.end()) + if (foundEntry != m_defines.end()) { - value = foundEntry->second; + value = &foundEntry->second; return true; } return false; } +void DefinesStreamProxy::ExtractParametersFromDefineUsage(const ParserLine& line, const unsigned parameterStart, unsigned& parameterEnd, std::vector& parameterValues) +{ + if (line.m_line[parameterStart] != '(') + return; + + std::ostringstream currentValue; + auto pos = parameterStart + 1; + auto valueHasStarted = false; + while (true) + { + if (pos >= line.m_line.size()) + throw ParsingException(CreatePos(line, pos), "Invalid use of define"); + + const auto c = line.m_line[pos]; + + if (c == ',') + { + parameterValues.emplace_back(currentValue.str()); + currentValue.clear(); + currentValue.str(std::string()); + valueHasStarted = false; + } + else if (c == ')') + { + parameterValues.emplace_back(currentValue.str()); + parameterEnd = pos + 1; + break; + } + else if (valueHasStarted || !isspace(c)) + { + valueHasStarted = true; + currentValue << c; + } + + pos++; + } +} + void DefinesStreamProxy::ExpandDefines(ParserLine& line) { auto wordStart = 0u; auto lastWordEnd = 0u; auto inWord = false; - std::string value; + Define* value; std::ostringstream str; auto usesDefines = false; @@ -181,14 +371,18 @@ void DefinesStreamProxy::ExpandDefines(ParserLine& line) { 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) << value; + str << std::string(line.m_line, 0, wordStart) << defineValue; usesDefines = true; } else { - str << std::string(line.m_line, lastWordEnd, wordStart - lastWordEnd) << value; + str << std::string(line.m_line, lastWordEnd, wordStart - lastWordEnd) << defineValue; } lastWordEnd = i; } @@ -201,20 +395,23 @@ void DefinesStreamProxy::ExpandDefines(ParserLine& line) { if (FindDefineForWord(line, wordStart, line.m_line.size(), value)) { + std::vector parameterValues; + const auto defineValue = value->Render(parameterValues); + if (!usesDefines) { - str << std::string(line.m_line, 0, wordStart) << value; + str << std::string(line.m_line, 0, wordStart) << defineValue; usesDefines = true; } else { - str << std::string(line.m_line, lastWordEnd, wordStart - lastWordEnd) << value; + str << std::string(line.m_line, lastWordEnd, wordStart - lastWordEnd) << defineValue; } lastWordEnd = line.m_line.size(); } } - if(usesDefines) + if (usesDefines) { if (lastWordEnd < line.m_line.size()) str << std::string(line.m_line, lastWordEnd, line.m_line.size() - lastWordEnd); @@ -222,9 +419,9 @@ void DefinesStreamProxy::ExpandDefines(ParserLine& line) } } -void DefinesStreamProxy::AddDefine(const std::string& name, std::string value) +void DefinesStreamProxy::AddDefine(Define define) { - m_defines[name] = std::move(value); + m_defines[define.m_name] = std::move(define); } void DefinesStreamProxy::Undefine(const std::string& name) diff --git a/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.h b/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.h index 81f16167..961366ae 100644 --- a/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.h +++ b/src/ZoneCodeGeneratorLib/Parsing/Impl/DefinesStreamProxy.h @@ -15,25 +15,52 @@ class DefinesStreamProxy final : public AbstractDirectiveStreamProxy static constexpr const char* ELSE_DIRECTIVE = "else"; static constexpr const char* ENDIF_DIRECTIVE = "endif"; +public: + class DefineParameterPosition + { + public: + unsigned m_parameter_index; + unsigned m_parameter_position; + + DefineParameterPosition(); + DefineParameterPosition(unsigned index, unsigned position); + }; + + class Define + { + public: + std::string m_name; + std::string m_value; + std::vector m_parameter_positions; + + Define(); + Define(std::string name, std::string value); + void IdentifyParameters(std::vector& parameterNames); + std::string Render(std::vector& parameterValues); + }; + +private: IParserLineStream* const m_stream; - std::unordered_map m_defines; + std::unordered_map m_defines; std::stack m_modes; unsigned m_ignore_depth; + std::vector MatchDefineParameters(const ParserLine& line, unsigned& parameterPosition); _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 ExtractParametersFromDefineUsage(const ParserLine& line, unsigned parameterStart, unsigned& parameterEnd, std::vector& parameterValues); + bool FindDefineForWord(const ParserLine& line, unsigned wordStart, unsigned wordEnd, Define*& value); void ExpandDefines(ParserLine& line); public: explicit DefinesStreamProxy(IParserLineStream* stream); - void AddDefine(const std::string& name, std::string value); + void AddDefine(Define define); void Undefine(const std::string& name); ParserLine NextLine() override; diff --git a/test/ZoneCodeGeneratorLibTests/Parsing/Impl/DefinesStreamProxyTests.cpp b/test/ZoneCodeGeneratorLibTests/Parsing/Impl/DefinesStreamProxyTests.cpp index b722aad9..941c8cc4 100644 --- a/test/ZoneCodeGeneratorLibTests/Parsing/Impl/DefinesStreamProxyTests.cpp +++ b/test/ZoneCodeGeneratorLibTests/Parsing/Impl/DefinesStreamProxyTests.cpp @@ -355,4 +355,119 @@ namespace test::parsing::impl::defines_stream_proxy REQUIRE(proxy.Eof()); } + + TEST_CASE("DefinesStreamProxy: Ensure define can render parameters", "[parsing][parsingstream]") + { + DefinesStreamProxy::Define define("helloworld", "hello universe"); + + std::vector parameterNames({ + "universe" + }); + define.IdentifyParameters(parameterNames); + + std::vector parameterValues({ + "mr moneyman" + }); + REQUIRE(define.Render(parameterValues) == "hello mr moneyman"); + } + + TEST_CASE("DefinesStreamProxy: Ensure define can render parameters in middle of symbols", "[parsing][parsingstream]") + { + DefinesStreamProxy::Define define("helloworld", "alignas(x)"); + + std::vector parameterNames({ + "x" + }); + define.IdentifyParameters(parameterNames); + + std::vector parameterValues({ + "1337" + }); + REQUIRE(define.Render(parameterValues) == "alignas(1337)"); + } + + 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" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "struct alignas(1337) test_struct"); + + REQUIRE(proxy.Eof()); + } + + 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" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "struct 1337|1337|1337|1337 test_struct"); + + REQUIRE(proxy.Eof()); + } + + 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)%" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "%5|5|5|5%"); + + REQUIRE(proxy.Eof()); + } + + 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);" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "make calc 1 + 2 = 3;"); + + REQUIRE(proxy.Eof()); + } + + 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);" + }; + + MockParserLineStream mockStream(lines); + DefinesStreamProxy proxy(&mockStream); + + ExpectLine(&proxy, 1, ""); + ExpectLine(&proxy, 2, "make calc 1+2=3;"); + + REQUIRE(proxy.Eof()); + } }