Files
openvehicle-api/sdv_executables/sdv_idl_compiler/entities/union_entity.cpp

448 lines
20 KiB
C++
Raw Normal View History

2026-03-27 14:12:49 +01:00
/********************************************************************************
* 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 "union_entity.h"
#include "../exception.h"
#include "../logger.h"
#include "../parser.h"
#include "typedef_entity.h"
#include "variable_entity.h"
#include <iostream>
#include <set>
CCaseEntry::CCaseEntry(const CContextPtr& rptrContext, CEntityPtr ptrParent, bool bDefault) :
CVariableEntity(rptrContext, ptrParent, false, false), m_bDefault(bDefault)
{}
std::string CCaseEntry::GetDeclTypeStr(bool /*bResolveTypedef*/) const
{
return GetScopedName();
}
sdv::interface_t CCaseEntry::GetInterface(sdv::interface_id idInterface)
{
if (idInterface == sdv::GetInterfaceId<sdv::IInterfaceAccess>())
return static_cast<sdv::IInterfaceAccess*>(this);
if (idInterface == sdv::GetInterfaceId<sdv::idl::ICaseEntity>())
return static_cast<sdv::idl::ICaseEntity*>(this);
return CVariableEntity::GetInterface(idInterface);
}
sdv::u8string CCaseEntry::GetLabel() const
{
// The default case entry doesn#t have a label.
if (m_bDefault) return sdv::u8string();
// Get the label string from the value of the case label entity.
CVariableEntity* pLabelVariableEntity = m_ptrLabel->Get<CVariableEntity>();
if (!pLabelVariableEntity)
throw CCompileException("Internal error: expected a variable entity as case label.");
if (!pLabelVariableEntity->ValueRef())
throw CCompileException("Internal error: expected a value for the variable entity case label.");
// Enum value node?
const CEnumValueNode* pEnumValueNode = pLabelVariableEntity->ValueRef()->Get<CEnumValueNode>();
if (pEnumValueNode) return pEnumValueNode->String();
// Simple value node?
const CSimpleTypeValueNode* pSimpleTypeValueNode = pLabelVariableEntity->ValueRef()->Get<CSimpleTypeValueNode>();
if (pSimpleTypeValueNode) return pSimpleTypeValueNode->Variant().GetAsString();
// Otherwise error...
throw CCompileException("Internal error: expected a value for the variable entity case label.");
}
const CToken& CCaseEntry::GetLabelToken() const
{
return m_tokenLabel;
}
void CCaseEntry::Process()
{
CLog log("Processing case label...");
// Peek for the label token (used for reporting parsing errors).
m_tokenLabel = PeekToken();
// Processing of the case label value is done in post processing since the switch type might not be known yet.
CToken token;
std::string ssCaseLabel;
while ((token = GetToken()) != ":")
{
if (!ssCaseLabel.empty())
ssCaseLabel += ' ';
ssCaseLabel += static_cast<std::string>(token);
m_lstCaseValue.push_back(token);
}
log << "Case label name '" << ssCaseLabel << "'" << std::endl;
// Only unnamed nested struct and union definitions are not allowed. Furthermore, anonymous structs and unions (without
// explicit declaration cannot be supported (also not part of the C++ standard). Varable based switch type unions are
// also not supported since the variable for the switch needs to be part of the parent switch case and thus needs a struct to
// hold it.
token = PeekToken();
if ((token == "struct" && (PeekToken(1) == "{" || PeekToken(2) == "{")) ||
(token == "union" && (PeekToken(1) == "switch" || PeekToken(2) == "switch")))
{
// Get the struct/union from the code
token = GetToken();
throw CCompileException(token, "Cannot make a definition inside an union.");
}
// Stop processing if the case doesn't have any declaration (is followed by another case or a closing curled bracket).
if (token == "case" || token == "}")
{
// Determine whether the comments are preceding the token (either on the same line or the line before).
CTokenList lstPreComments = GetPreCommentTokenList();
if (!lstPreComments.empty()) SetCommentTokens(lstPreComments);
// Assign any succeeding comments
ProcessPostCommentTokenList(token.GetLine());
// Insert a semi-colon to identify that the statement is finished.
CToken tokenSep(";", ETokenType::token_separator);
PrependToken(tokenSep);
SetName(GetParserRef().GenerateAnonymousEntityName("case"));
return;
}
// Now let the variable process the declaration
CVariableEntity::Process();
}
void CCaseEntry::PostProcess()
{
CLog log("Post processing case label...");
// Get the base type of the enum entity and insert it in front of the declaration.
CUnionEntity* pUnionEntity = GetParentEntity()->Get<CUnionEntity>();
if (!pUnionEntity) throw CCompileException("Internal error: expected an union entity as parent.");
// Separate between case statement and default
if (m_bDefault)
{
if (!m_lstCaseValue.empty())
throw CCompileException("Default case label cannot have a value.");
log << "Default case label" << std::endl;
} else
{
// Get the switch case type
sdv::idl::EDeclType eSwitchCaseType = sdv::idl::EDeclType::decltype_unknown;
CEntityPtr ptrSwitchCaseType;
CValueNodePtr ptrSwitchCaseValue;
pUnionEntity->GetSwitchCaseType(eSwitchCaseType, ptrSwitchCaseType, ptrSwitchCaseValue);
// Insert the switch type specific assignment to the token list
m_lstCaseValue.push_front(CToken("="));
m_lstCaseValue.push_front(CToken("label"));
if (!ptrSwitchCaseType)
{
switch (eSwitchCaseType)
{
case sdv::idl::EDeclType::decltype_short: m_lstCaseValue.push_front(CToken("short")); break;
case sdv::idl::EDeclType::decltype_long: m_lstCaseValue.push_front(CToken("long")); break;
case sdv::idl::EDeclType::decltype_long_long: m_lstCaseValue.push_front(CToken("long long")); break;
case sdv::idl::EDeclType::decltype_unsigned_short: m_lstCaseValue.push_front(CToken("unsigned short")); break;
case sdv::idl::EDeclType::decltype_unsigned_long: m_lstCaseValue.push_front(CToken("unsigned long")); break;
case sdv::idl::EDeclType::decltype_unsigned_long_long: m_lstCaseValue.push_front(CToken("unsigned long long")); break;
case sdv::idl::EDeclType::decltype_octet: m_lstCaseValue.push_front(CToken("octet")); break;
case sdv::idl::EDeclType::decltype_char: m_lstCaseValue.push_front(CToken("char")); break;
case sdv::idl::EDeclType::decltype_char16: m_lstCaseValue.push_front(CToken("char16")); break;
case sdv::idl::EDeclType::decltype_char32: m_lstCaseValue.push_front(CToken("char32")); break;
case sdv::idl::EDeclType::decltype_wchar: m_lstCaseValue.push_front(CToken("wchar")); break;
case sdv::idl::EDeclType::decltype_boolean: m_lstCaseValue.push_front(CToken("boolean")); break;
case sdv::idl::EDeclType::decltype_native: m_lstCaseValue.push_front(CToken("native")); break;
default: throw CCompileException(m_tokenLabel, "Internal error: invalid switch case data type.");
}
}
else
m_lstCaseValue.push_front(CToken(ptrSwitchCaseType->GetScopedName()));
m_lstCaseValue.push_back(CToken(";", ETokenType::token_separator));
// Create the label value.
m_ptrLabel = std::make_shared<CVariableEntity>(GetContext(), shared_from_this(), m_lstCaseValue, true, false);
// Process the label variable (this will resolve any assigned value and constant).
m_ptrLabel->Process();
CVariableEntity* pLabelVariableEntity = m_ptrLabel->Get<CVariableEntity>();
if (!pLabelVariableEntity)
throw CCompileException("Internal error: expected a variable entity as case label.");
pLabelVariableEntity->PostProcess();
log << "Case label is: " << GetLabel() << std::endl;
}
// Post process the assignment
CVariableEntity::PostProcess();
}
CUnionEntity::CUnionEntity(const CContextPtr& rptrContext, CEntityPtr ptrParent) :
CStructEntity(rptrContext, ptrParent)
{}
sdv::interface_t CUnionEntity::GetInterface(sdv::interface_id idInterface)
{
if (idInterface == sdv::GetInterfaceId<sdv::IInterfaceAccess>())
return static_cast<sdv::IInterfaceAccess*>(this);
if (idInterface == sdv::GetInterfaceId<sdv::idl::IUnionEntity>())
return static_cast<sdv::idl::IUnionEntity*>(this);
return CStructEntity::GetInterface(idInterface);
}
sdv::idl::IUnionEntity::ESwitchInterpret CUnionEntity::GetSwitchInterpretation() const
{
return m_eSwitchInterpret;
}
void CUnionEntity::GetSwitchType(/*out*/ sdv::idl::EDeclType& reType, /*out*/ sdv::IInterfaceAccess*& rpType) const
{
reType = m_typedeclSwitch.GetBaseType();
rpType = m_typedeclSwitch.GetTypeDefinition();
}
void CUnionEntity::GetSwitchVar(/*out*/ sdv::u8string& rssVarStr, /*out*/ sdv::IInterfaceAccess*& rpVarEntity,
/*out*/ sdv::IInterfaceAccess*& rpVarContainer) const
{
rssVarStr.clear();
rpVarEntity = nullptr;
rpVarContainer = nullptr;
if (m_eSwitchInterpret == sdv::idl::IUnionEntity::ESwitchInterpret::switch_type) return;
rssVarStr = m_ssValueNode;
rpVarEntity = m_ptrSwitchValueNode ? m_ptrSwitchValueNode->GetDeclEntity()->Get<sdv::IInterfaceAccess>() : nullptr;
rpVarContainer = m_ptrContainer ? m_ptrContainer->Get<sdv::IInterfaceAccess>() : nullptr;
}
std::string CUnionEntity::GetDeclTypeStr(bool /*bResolveTypedef*/) const
{
return std::string("union ") + GetScopedName();
}
void CUnionEntity::Process()
{
// The definition and declaration can be defined:
// union <union_identifier>; --> forward declaration
// union <union_identifier> switch(<type>) {...}; --> union definition with type
// struct { <type> <var>; union <union_identifier> switch(<var>) {...};}; --> union definition with varaiable
// <union_identifier> <decl_identifier>; --> union variable declaration
// <union_identifier> <decl_identifier> = {...}; --> union variable declaration and assignment
// union <union_identifier> <decl_identifier>; --> union variable declaration
// union <union_identifier> <decl_identifier> = {...}; --> union variable declaration and assignment
// union <union_identifier> {...} <decl_identifier>; --> union definition and variable declaration
// union <union_identifier> {...} <decl_identifier> = {...}; --> union definition, variable declaration and assignment
// union {...} <decl_identifier>; --> anonymous union definition and variable declaration
// union {...} <decl_identifier> = {...}; --> anonymous union definition, variable declaration and assignment
// typedef <union_identifier> <type_identifier>;
// typedef union <union_identifier> { ... } <type_identifier>;
// typedef <union_identifier> <type_identifier>;
// typedef union <union_identifier> { ... } <type_identifier>;
// typedef union { ... } <type_identifier>;
// const union <union_identifier> <decl_identifier> = {...};
// const <union_identifier> <decl_identifier> = {...};
// const union {...} <decl_identifier> = {...};
// The declaration is as follows:
// <union_identifier> <decl_identifier>;
// union <union_identifier> <decl_identifier>;
// union <union_identifier> { ... } <decl_identifier>;
CStructEntity::Process();
}
void CUnionEntity::ProcessDefinitionAddendum()
{
//unions are defined as :
//union <union_identifier> switch (<type>)-- > type is a integral or enum type or a typedef of this
//{
//case <value>: <type> <var_identifier> -- > case values need to be unique
// ...
//};
//struct
//{
// <type> <var>;
// union <union_identifier> switch (<var>)-- > var is an integral or enum value, which is part of the struct
// {
// case <value>: <type> <var_identifier> -- > case values need to be unique
// ...
// };
//};
if (PeekToken() == ";") return; // Forward declaration
CToken token = GetToken();
if (token != "switch") throw CCompileException(token, "Expecting a switch statement following the union identifier.");
token = GetToken();
if (token != "(") throw CCompileException(token, "Expecting a left bracket '('.");
size_t nIndex = 0;
while (true)
{
token = PeekToken(nIndex++);
if (!token || token == ")") break;
m_ssSwitchVar += static_cast<std::string>(token);
}
m_typedeclSwitch = ProcessType(true);
token = GetToken();
while (token && token != ")")
{
m_ssValueNode += static_cast<std::string>(token);
token = GetToken();
}
if (token != ")") throw CCompileException(token, "Expecting a right bracket ')'.");
// Was it possible to resolve the type for the switch case. If not, the switch case is a variable?
if (m_typedeclSwitch.GetBaseType() == sdv::idl::EDeclType::decltype_unknown)
m_eSwitchInterpret = sdv::idl::IUnionEntity::ESwitchInterpret::switch_variable;
}
void CUnionEntity::PostProcess()
{
if (ForwardDeclaration()) return;
// Was the switch case type/variable found before? If not, check for a variable.
if (m_eSwitchInterpret == sdv::idl::IUnionEntity::ESwitchInterpret::switch_variable)
{
// Get the switch type object
CEntityPtr ptrSwitchVarBase = Find(m_typedeclSwitch.GetTypeString());
if (!ptrSwitchVarBase)
throw CCompileException(m_ssSwitchVar,
"The switch case must be determined by a predefined type or a member variable.");
// Proper relative name (without global scope)
std::string ssSwitchFullName = ptrSwitchVarBase->GetName() + m_ssValueNode;
m_ssValueNode = ssSwitchFullName;
// The type must be a variable type
CVariableEntity* pVarEntityBase = ptrSwitchVarBase->Get<CVariableEntity>();
if (!pVarEntityBase)
throw CCompileException(m_ssSwitchVar,
"The switch case is not determined by a member variable nor a predefined type.");
// The parent of the base is the common parent of both the switch var and the union.
m_ptrContainer = ptrSwitchVarBase->GetParentEntity();
if (!m_ptrContainer)
throw CCompileException(m_ssSwitchVar,
"The switch case variable and the union do not share a common parent.");
// Find the value node of the switch variable (in case it is a child some layers deep).
CValueNodePtr ptrSwitchValueNode;
if (!m_ssValueNode.empty())
m_ptrSwitchValueNode = pVarEntityBase->FindValue(m_ssValueNode);
else
m_ptrSwitchValueNode = pVarEntityBase->ValueRef();
if (!m_ptrSwitchValueNode)
throw CCompileException(m_ssSwitchVar, "Could not find the switch variable.");
// Change the variable type entity to a switch variable.
m_ptrSwitchValueNode->GetDeclEntity()->Get<CVariableEntity>()->UseAsSwitchVariable();
// Assign the type of the variable.
m_typedeclSwitch.SetTypeDefinitionEntityPtr(m_ptrSwitchValueNode->GetDeclEntity()->GetTypeEntity());
m_typedeclSwitch.SetBaseType(m_ptrSwitchValueNode->GetDeclEntity()->GetBaseType());
if (!IsIntegralDeclType(m_typedeclSwitch.GetBaseType()) &&
m_typedeclSwitch.GetBaseType() != sdv::idl::EDeclType::decltype_enum)
throw CCompileException(m_ssSwitchVar, "Expecting an integral or enum identifier type or variable.");
// Check whether one of the parents is a struct or a union and the variable is a struct member.
CEntityPtr ptrParent = GetParentEntity();
if (!ptrParent ||
((ptrParent->GetType() != sdv::idl::EEntityType::type_struct) &&
(ptrParent->GetType() != sdv::idl::EEntityType::type_exception)))
throw CCompileException(m_ssSwitchVar,
"The union needs to be part of a struct or an exception when used with a variable based switch case.");
while (ptrSwitchVarBase)
{
if (!ptrParent ||
((ptrParent->GetType() != sdv::idl::EEntityType::type_struct) &&
(ptrParent->GetType() != sdv::idl::EEntityType::type_exception)))
throw CCompileException(m_ssSwitchVar,
"The variable used in the switch case must be a member of the same or parent struct or exception the union "
"is declared in.");
if (ptrParent->GetType() == sdv::idl::EEntityType::type_struct &&
ptrParent == ptrSwitchVarBase->GetParentEntity())
break;
ptrParent = ptrParent->GetParentEntity();
}
}
// Post process all the case statements
std::set<std::string> setValues;
bool bDefaultFound = false;
for (CEntityPtr ptrEntity : m_lstDeclMembers)
{
CCaseEntry* pCaseEntry = ptrEntity->Get<CCaseEntry>();
if (!pCaseEntry) throw CCompileException("Internal error: unexpected entity stored at union.");
pCaseEntry->PostProcess();
// Differentiate between default and standard case label...
if (pCaseEntry->IsDefault())
{
if (bDefaultFound)
throw CCompileException(pCaseEntry->GetLabelToken(), "Duplicate default switch found.");
bDefaultFound = true;
} else
{
// Check for the existence of the label.
std::string ssLabel = pCaseEntry->GetLabel();
if (m_setValues.find(ssLabel) != m_setValues.end())
throw CCompileException(pCaseEntry->GetLabelToken(), "Duplicate switch case label found.");
m_setValues.insert(ssLabel);
}
}
// If supported create the value node for the definition (this allows assignments of values to this entity).
CreateValueNode();
}
bool CUnionEntity::Supports(EDefinitionSupport eSupport) const
{
switch (eSupport)
{
case EDefinitionSupport::support_case_declaration: return true;
case EDefinitionSupport::support_variable: return false;
case EDefinitionSupport::support_const_variable: return false;
case EDefinitionSupport::support_typedef: return false;
case EDefinitionSupport::support_struct: return false;
case EDefinitionSupport::support_union: return false;
case EDefinitionSupport::support_enum: return false;
default: return false;
}
}
void CUnionEntity::CreateValueNode()
{
// Create a compound type value node for this definition.
ValueRef() = std::make_shared<CCompoundTypeValueNode>(shared_from_this(), nullptr);
}
bool CUnionEntity::RequireDeclaration() const
{
// Require a declaration when unnamed union.
return !ForwardDeclaration() && IsUnnamed();
}
bool CUnionEntity::AllowAutoTransparentDeclaration() const
{
return !ForwardDeclaration() && IsUnnamed();
}
void CUnionEntity::GetSwitchCaseType(sdv::idl::EDeclType& reType, CEntityPtr& rptrType, CValueNodePtr& rptrValue)
{
reType = m_typedeclSwitch.GetBaseType();
rptrType = m_typedeclSwitch.GetTypeDefinitionEntityPtr();
if (m_ptrSwitchValueNode) rptrValue = m_ptrSwitchValueNode;
}