/******************************************************************************** * Copyright (c) 2025-2026 ZF Friedrichshafen AG * * This program and the accompanying materials are made available under the * terms of the Apache License Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0 * * SPDX-License-Identifier: Apache-2.0 * * Contributors: * Erik Verhoeven - initial API and implementation ********************************************************************************/ #include "macro.h" #include "exception.h" #include "lexer.h" #include "environment.h" #include /** * @brief Callback class for the lexer allowing to insert the whitespace and comments into the provided stream. */ class CMacroResolveCallback : public ILexerCallback { public: /** * @brief Constructor providing a reference of the resolved string to use for concatenating comments and whitepace. * @param[in] rssName Reference to the macro name. * @param[in] rssTargetValue Reference to the value. * @param[in] rbSuppressWhitespace Reference to a boolean stating whether to suppress whitespace. */ CMacroResolveCallback(const std::string& rssName, std::string& rssTargetValue, const bool& rbSuppressWhitespace) : m_ssName(rssName), m_rssTargetValue(rssTargetValue), m_rbSuppressWhitespace(rbSuppressWhitespace) {} /** * @brief Insert whitespace. Overload of ILexerCallback::InsertWhitespace. */ virtual void InsertWhitespace(const CToken&) override { // Multiple spaces are reduced to one space. if (!m_rbSuppressWhitespace) m_rssTargetValue += ' '; } /** * @brief Insert a comment. Overload of ILexerCallback::InsertComment. */ virtual void InsertComment(const CToken&) override { // Comments are ignored } /** * @brief Process a preprocessor directive. Overload of ILexerCallback::ProcessPreprocDirective. */ virtual void ProcessPreprocDirective(CCodePos& /*rCode*/) override { // Should not be called. throw CCompileException("Internal error trying to resolve the ", m_ssName, " macro."); } private: std::string m_ssName; //!< Macro name. std::string& m_rssTargetValue; //!< Reference to the string to concatenate the comments and whitespace onto. const bool& m_rbSuppressWhitespace; //!< Reference to a boolean stating whether to prevent concatination of whitespace. }; CMacro::CMacro(const char* szName, const std::vector* pvecParams, const char* szValue) : m_ssName(szName), m_bExpectParams(pvecParams ? true : false), m_vecParamDefs(pvecParams ? *pvecParams : std::vector()), m_ssValue(szValue ? szValue : "") { // Remove any whitespace from the end of the value. while (m_ssValue.size() && std::isspace(m_ssValue.back())) m_ssValue.erase(m_ssValue.size() - 1); // Remove any whitespace from the beginning of the value. while (m_ssValue.size() && std::isspace(m_ssValue.front())) m_ssValue.erase(0, 1); } CMacro::CMacro(const CMacro& rMacro) : m_ssName(rMacro.m_ssName), m_bExpectParams(rMacro.m_bExpectParams), m_vecParamDefs(rMacro.m_vecParamDefs), m_ssValue(rMacro.m_ssValue) {} CMacro::CMacro(CMacro&& rMacro) noexcept : m_ssName(std::move(rMacro.m_ssName)), m_bExpectParams(rMacro.m_bExpectParams), m_vecParamDefs(std::move(rMacro.m_vecParamDefs)), m_ssValue(std::move(rMacro.m_ssValue)) { rMacro.m_bExpectParams = false; } CMacro& CMacro::operator=(const CMacro& rMacro) { m_ssName = rMacro.m_ssName; m_bExpectParams = rMacro.m_bExpectParams; m_vecParamDefs = rMacro.m_vecParamDefs; m_ssValue = rMacro.m_ssValue; return *this; } CMacro& CMacro::operator=(CMacro&& rMacro) noexcept { m_ssName = std::move(rMacro.m_ssName); m_bExpectParams = rMacro.m_bExpectParams; m_vecParamDefs = std::move(rMacro.m_vecParamDefs); m_ssValue = std::move(rMacro.m_ssValue); rMacro.m_bExpectParams = false; return *this; } bool CMacro::operator==(const CMacro& rMacro) { if (m_bExpectParams != rMacro.m_bExpectParams) return false; if (m_ssName != rMacro.m_ssName) return false; if (m_bExpectParams && m_vecParamDefs != rMacro.m_vecParamDefs) return false; if (m_ssValue != rMacro.m_ssValue) return false; return true; } bool CMacro::operator!=(const CMacro& rMacro) { return !operator==(rMacro); } const std::string& CMacro::GetName() const { return m_ssName; } bool CMacro::ExpectParameters() const { return m_bExpectParams; } std::string CMacro::Expand(const CIdlCompilerEnvironment& renv, const CToken& rtoken, const std::vector& rvecParams, CUsedMacroSet& rsetUsedMacros) const { // Parameters in function like macros are expanded before being inserted into the value if not part of a stringification // operation. // Commments are ignored in a macro value. // Multiple spaces in the value are reduced to one space. // The result of the macro is expanded before being returned. // Circular references to macros within the expanded code are prevented for // - each paramerer separately with the used macro list provided by the function. // - macro expansion result with the used macros from all the parameters and the used macro list provided to the function. // Macros used in the expansion of the parameters and the results are added to the used macro set provided to this function. // Check whether the amount of provided params corresponds to the amount of param definitions. if (rvecParams.size() < m_vecParamDefs.size()) throw CCompileException(rtoken, "Missing parameters while calling macro"); if (rvecParams.size() > m_vecParamDefs.size()) throw CCompileException(rtoken, "Provided too many parameters while calling macro"); std::string ssTargetValue; bool bConcatenateNext = false; bool bStringificateNext = false; CMacroResolveCallback callback(m_ssName, ssTargetValue, bConcatenateNext); CLexer lexer(&callback, renv.CaseSensitiveTypeExtension(), CLexer::ELexingMode::lexing_preproc); CCodePos codeValue(m_ssValue.c_str()); // The set with used macros for all parameters should stay the same, wheresas the provided set should be extended. CUsedMacroSet setUsedMacrosStored = rsetUsedMacros; // Stringificate the supplied string. auto fnStringificate = [](const std::string &rss) { std::string ssTarget; ssTarget += '\"'; for (char cVal : rss) { switch (cVal) { case '\"': ssTarget += "\\\""; break; case '\'': ssTarget += "\\\'"; break; case '\a': ssTarget += "\\a"; break; case '\b': ssTarget += "\\b"; break; case '\f': ssTarget += "\\f"; break; case '\n': ssTarget += "\\n"; break; case '\r': ssTarget += "\\r"; break; case '\t': ssTarget += "\\t"; break; case '\v': ssTarget += "\\v"; break; default: ssTarget += cVal; break; } } ssTarget += '\"'; return ssTarget; }; // Expand the supplied code auto fnExpand = [&](const char* szCode) -> std::string { // Expand the parameter before inserting into the value. std::string ssTarget; bool bConcatenateDummy = false; CMacroResolveCallback callbackParam(m_ssName, ssTarget, bConcatenateDummy); CLexer lexerLocal(&callbackParam, renv.CaseSensitiveTypeExtension(), CLexer::ELexingMode::lexing_preproc); CCodePos code(szCode); // Create a copy of the set of used macros to provide this to the parameter expansion. These are the stored // original used macros. CUsedMacroSet setUsedMacrosParam = setUsedMacrosStored; // Expand the parameter while (!code.HasEOF()) { // If the current position is not part of the macro expansion, reset the set of macros used in a expansion. bool bInMacroExpansion = code.CurrentPositionInMacroExpansion(); // In case the code is not part of the macro expansion, use the stored set of used macros provided to this function. // Otherwise the set still contains the macros used in the expansion. if (!bInMacroExpansion) setUsedMacrosParam = setUsedMacrosStored; // Get a token. CToken token = lexerLocal.GetToken(code, rtoken.GetContext()); // Check whether the token is an identifier, if so, check for any macro if (token.GetType() == ETokenType::token_identifier) { // Test and expand the if (renv.TestAndExpand(static_cast(token).c_str(), code, bInMacroExpansion, setUsedMacrosParam)) { // Add all the used macros of the parameter expansion to the set of used macros provided to this function. for (const std::string& rssMacro : setUsedMacrosStored) rsetUsedMacros.insert(rssMacro); continue; // macro was replaced, get a token again. } } ssTarget += static_cast(token); } return ssTarget; }; // Parse through the value and deal with parameter names, with stringification operators '#' and concatenating operators '##'. while (!codeValue.HasEOF()) { CToken token = lexer.GetToken(codeValue, rtoken.GetContext()); // Check for concatinating token if (token == "##") { // Concatination and stringification is not allowed to be selected already before. if (bConcatenateNext) throw CCompileException(rtoken, "Double concatinating operator while resolving macro ", m_ssName); if (bStringificateNext) throw CCompileException(rtoken, "Cannot stringificate and then concatenate the result while resolving macro ", m_ssName); // Remove whitespace from end of the target value std::string::reverse_iterator itEnd = std::find_if(ssTargetValue.rbegin(), ssTargetValue.rend(), [](char c) { return !std::isspace(c, std::locale::classic()); }); ssTargetValue.erase(itEnd.base(), ssTargetValue.end()); // Set the concatination flag (this will also prevent concatinating whitespace to the target). bConcatenateNext = true; // Next processing continue; } // Check for stringification token if (token == "#") { // Multiple stringification tokens following each other is not allowed. if (bStringificateNext) throw CCompileException(rtoken, "Double stringification operator while resolving macro ", m_ssName); // Set the stringification flag and enable concatination to prevent whitespace to be inserted. bStringificateNext = true; bConcatenateNext = true; // Next processing continue; } // Check whether the token represents a parameter name if (token.GetType() == ETokenType::token_identifier) { // Check if the token corresponds to one of the parameters. size_t nParamPos = 0; for (; nParamPos < m_vecParamDefs.size(); nParamPos++) if (m_vecParamDefs[nParamPos] == static_cast(token)) break; if (nParamPos < m_vecParamDefs.size()) { // Add the parameter content to the target. Stringitificate if needed. if (bStringificateNext) ssTargetValue += fnStringificate(rvecParams[nParamPos]); else ssTargetValue += fnExpand(rvecParams[nParamPos].c_str()); } else { // Stringification is not allowed when not using a parameter. if (bStringificateNext) throw CCompileException(rtoken, "Cannot stringificate while resolving macro ", m_ssName); // Add the token to the target ssTargetValue += static_cast(token); } } else { // Stringification is not allowed when not using a parameter. if (bConcatenateNext) throw CCompileException(rtoken, "Cannot stringificate while resolving macro ", m_ssName); // Add the token to the target ssTargetValue += static_cast(token); } // Stringification and concatenating finalized. bConcatenateNext = false; bStringificateNext = false; } // The provided set of used macros now contain all the used macros by the parent, this macro and the macros of any of the // parameters. Use this set for the expansion of the result - this will prevent circular calling of macros already used // in parameters. setUsedMacrosStored = rsetUsedMacros; // Return the expanded the macro result. This will automatically add any macros expanded in the result to the used macro set. return fnExpand(ssTargetValue.c_str()); }