mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-02-05 15:18:45 +00:00
343 lines
13 KiB
C++
343 lines
13 KiB
C++
|
|
#include "macro.h"
|
||
|
|
#include "exception.h"
|
||
|
|
#include "lexer.h"
|
||
|
|
#include "environment.h"
|
||
|
|
#include <algorithm>
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @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<std::string>* pvecParams, const char* szValue) :
|
||
|
|
m_ssName(szName),
|
||
|
|
m_bExpectParams(pvecParams ? true : false),
|
||
|
|
m_vecParamDefs(pvecParams ? *pvecParams : std::vector<std::string>()),
|
||
|
|
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<std::string>& 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 paramters 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<std::string>(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<std::string>(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<char>(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<std::string>(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<std::string>(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<std::string>(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());
|
||
|
|
}
|
||
|
|
|