/******************************************************************************** * 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 #include 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()) return static_cast(this); if (idInterface == sdv::GetInterfaceId()) return static_cast(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(); 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(); if (pEnumValueNode) return pEnumValueNode->String(); // Simple value node? const CSimpleTypeValueNode* pSimpleTypeValueNode = pLabelVariableEntity->ValueRef()->Get(); 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(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(); 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(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(); 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()) return static_cast(this); if (idInterface == sdv::GetInterfaceId()) return static_cast(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() : nullptr; rpVarContainer = m_ptrContainer ? m_ptrContainer->Get() : nullptr; } std::string CUnionEntity::GetDeclTypeStr(bool /*bResolveTypedef*/) const { return std::string("union ") + GetScopedName(); } void CUnionEntity::Process() { // The definition and declaration can be defined: // union ; --> forward declaration // union switch() {...}; --> union definition with type // struct { ; union switch() {...};}; --> union definition with varaiable // ; --> union variable declaration // = {...}; --> union variable declaration and assignment // union ; --> union variable declaration // union = {...}; --> union variable declaration and assignment // union {...} ; --> union definition and variable declaration // union {...} = {...}; --> union definition, variable declaration and assignment // union {...} ; --> anonymous union definition and variable declaration // union {...} = {...}; --> anonymous union definition, variable declaration and assignment // typedef ; // typedef union { ... } ; // typedef ; // typedef union { ... } ; // typedef union { ... } ; // const union = {...}; // const = {...}; // const union {...} = {...}; // The declaration is as follows: // ; // union ; // union { ... } ; CStructEntity::Process(); } void CUnionEntity::ProcessDefinitionAddendum() { //unions are defined as : //union switch ()-- > type is a integral or enum type or a typedef of this //{ //case : -- > case values need to be unique // ... //}; //struct //{ // ; // union switch ()-- > var is an integral or enum value, which is part of the struct // { // case : -- > 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(token); } m_typedeclSwitch = ProcessType(true); token = GetToken(); while (token && token != ")") { m_ssValueNode += static_cast(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(); 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()->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 setValues; bool bDefaultFound = false; for (CEntityPtr ptrEntity : m_lstDeclMembers) { CCaseEntry* pCaseEntry = ptrEntity->Get(); 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(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; }