/******************************************************************************** * 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 "declaration_entity.h" #include "../exception.h" #include "../logger.h" #include "../token.h" #include "../tokenlist.h" #include "../support.h" #include "../lexer.h" #include "../constvariant.inl" #include "../parser.h" #include "struct_entity.h" #include "interface_entity.h" #include "typedef_entity.h" #include "variable_entity.h" #include "attribute_entity.h" #include "operation_entity.h" #include "exception_entity.h" #include "parameter_entity.h" #include "enum_entity.h" #include "union_entity.h" #include #include #include CDeclarationEntity::CDeclarationEntity(const CContextPtr& rptrContext, CEntityPtr ptrParent) : CEntity(rptrContext, ptrParent) {} CDeclarationEntity::CDeclarationEntity(const CContextPtr& rptrContext, CEntityPtr ptrParent, const CTokenList& rlstTokenList) : CEntity(rptrContext, ptrParent, rlstTokenList) {} sdv::interface_t CDeclarationEntity::GetInterface(sdv::interface_id idInterface) { if (idInterface == sdv::GetInterfaceId()) return static_cast(this); if (idInterface == sdv::GetInterfaceId()) return static_cast(this); return CEntity::GetInterface(idInterface); } sdv::IInterfaceAccess* CDeclarationEntity::GetDeclarationType() const { return const_cast(&m_typedecl); } CEntityPtr CDeclarationEntity::GetTypeEntity() const { return m_typedecl.GetTypeDefinitionEntityPtr(); } bool CDeclarationEntity::HasArray() const { return m_vecMultiArraySizeTokenList.size() ? true : false; } sdv::sequence CDeclarationEntity::GetArrayDimensions() const { sdv::sequence seqArrayDimensions; if (!HasArray()) return seqArrayDimensions; // Traverse through each array entry std::function fnCollectArrayDimensions = [&](const CValueNodePtr& ptrValue) { // Is the value node an array at all? CArrayValueNode* pArrayValue = ptrValue->Get(); if (!pArrayValue) return; // Fill the array dimension struct sdv::idl::SArrayDimension sArrayDimension{}; sArrayDimension.eType = sdv::idl::SArrayDimension::EDimensionType::bound; if (pArrayValue->IsUnbound()) sArrayDimension.eType = sdv::idl::SArrayDimension::EDimensionType::unbound; sArrayDimension.ssExpression = MakeFullScoped(pArrayValue->GetSizeExpression()); // Store in sequence seqArrayDimensions.push_back(sArrayDimension); // Process the next array dimension if (pArrayValue->GetSize() != 0) fnCollectArrayDimensions((*pArrayValue)[0]); }; // Collect the array dimensions. fnCollectArrayDimensions(ValueRef()); return seqArrayDimensions; } bool CDeclarationEntity::HasAssignment() const { return !m_lstAssignmentTokenList.empty(); } sdv::u8string CDeclarationEntity::GetAssignment() const { std::stringstream sstreamAssignment; bool bInitial = true; for (const CToken& rToken : m_lstAssignmentTokenList) { if (!bInitial) sstreamAssignment << " "; bInitial = false; sstreamAssignment << static_cast(rToken); } return sstreamAssignment.str(); } void CDeclarationEntity::Process() { CLog log("Processing declaration (preparation)..."); // 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); // Process the type CTypeDeclaration sTypeDecl = ProcessType(); // Check for the support of interface and void types if (sTypeDecl.GetBaseType() == sdv::idl::EDeclType::decltype_interface && !SupportInterface()) throw CCompileException("The declaration of interfaces is not supported."); if (sTypeDecl.GetBaseType() == sdv::idl::EDeclType::decltype_void && !SupportVoid()) throw CCompileException("The use of 'void' as type is not supported."); // Preprocess potential array declaration (only for operations). if (GetType() == sdv::idl::EEntityType::type_operation) PreprocessArrayDeclaration(); // Process the declaration ProcessDeclaration(sTypeDecl); } void CDeclarationEntity::ProcessDeclaration(const CTypeDeclaration& rTypeDecl) { // Store the type m_typedecl = rTypeDecl; CLog log("Processing declaration..."); // Expecting an identifier. CToken token = GetToken(); if (token.GetType() == ETokenType::token_keyword) { // Keywords as names are allowed if the extension is enabled. if (!GetParserRef().GetEnvironment().ContextDependentNamesExtension()) throw CCompileException(token, "The identifier cannot be a reserved keyword."); } else if (token.GetType() != ETokenType::token_identifier) throw CCompileException(token, "Expecting an identifier."); SetName(token); log << "Declaration name '" << GetName() << "'..." << std::endl; // Preprocess potential array declaration (not for operations). if (GetType() != sdv::idl::EEntityType::type_operation) PreprocessArrayDeclaration(); // Further processing... token = GetToken(); // Requires parameters? if (RequiresParameters()) { // Expect a left bracket if (token != "(") throw CCompileException(token, "Expected left bracket '('."); log << "Reading parameter list..." << std::endl; PreprocessTokenListVector(m_vecParametersTokenList); // Expect a right bracket token = GetToken(); if (token != ")") throw CCompileException(token, "Expected right bracket ')'."); // Get the next token... token = GetToken(); // Check for the 'const' keyword. If set, the operation is defined as const-operation. if (token == "const") { SetOperationAsConst(); token = GetToken(); } } // Supports exceptions if (SupportRaiseExceptions()) { while (true) { // Check for the 'raises' keyword. enum class EExceptType {raises, getraises, setraises, none} eExceptType = EExceptType::none; if (token == "raises") eExceptType = EExceptType::raises; else if (token == "getraises") eExceptType = EExceptType::getraises; else if (token == "setraises") eExceptType = EExceptType::setraises; if (eExceptType == EExceptType::none) break; // Check for validity if (!SupportSeparateSetGetRaiseExceptions() && eExceptType == EExceptType::getraises) throw CCompileException(token, "Cannot set a separate 'get-raises' exception list; use the 'raises' keyword instead."); if (!SupportSeparateSetGetRaiseExceptions() && eExceptType == EExceptType::setraises) throw CCompileException(token, "Cannot set a separate 'set-raises' exception list; use the 'raises' keyword instead."); if (SupportSeparateSetGetRaiseExceptions() && eExceptType == EExceptType::raises && IsReadOnly()) eExceptType = EExceptType::getraises; if (eExceptType == EExceptType::setraises && IsReadOnly()) throw CCompileException(token, "Cannot set a set-raises exception list for a readonly type."); if ((eExceptType == EExceptType::raises) && !m_vecRaisesExceptionsTokenList.empty()) throw CCompileException(token, "Multiple definitions of 'raises' exceptions are not allowed."); if ((eExceptType == EExceptType::setraises) && !m_vecSetRaisesExceptionsTokenList.empty()) throw CCompileException(token, "Multiple definitions of 'set-raises' exceptions are not allowed."); if ((eExceptType == EExceptType::getraises) && !m_vecGetRaisesExceptionsTokenList.empty()) throw CCompileException(token, "Multiple definitions of 'get-raises' exceptions are not allowed."); // Expect a left bracket token = GetToken(); if (token != "(") throw CCompileException(token, "Expected left bracket '('."); // Processes raises exception list. switch (eExceptType) { case EExceptType::raises: log << "Reading 'raises' exception list..." << std::endl; PreprocessTokenListVector(m_vecRaisesExceptionsTokenList); if (m_vecRaisesExceptionsTokenList.empty()) throw CCompileException(token, "Missing exception types."); break; case EExceptType::setraises: log << "Reading 'setraises' exception list..." << std::endl; PreprocessTokenListVector(m_vecSetRaisesExceptionsTokenList); if (m_vecSetRaisesExceptionsTokenList.empty()) throw CCompileException(token, "Missing exception types."); break; case EExceptType::getraises: log << "Reading 'getraises' exception list..." << std::endl; PreprocessTokenListVector(m_vecGetRaisesExceptionsTokenList); if (m_vecGetRaisesExceptionsTokenList.empty()) throw CCompileException(token, "Missing exception types."); break; default: break; } // Expect a right bracket token = GetToken(); if (token != ")") throw CCompileException(token, "Expected right bracket ')'."); // Get the next token... token = GetToken(); } } // Is there an assignment? if (token == "=") { if (!SupportAssignments()) throw CCompileException(token, "Assignment operator detected, but type doesn't support assignments."); log << "Declaration assignment detected; storing expression for later processing..." << std::endl; // Read the tokens for the assignment expression. Read until ';' or ','; the latter not within an expression // sub-statement. size_t nDepth = 0; log << "Assignment expression:" << std::endl; while(true) { token = GetToken(); if (!token) throw CCompileException("Unexpected end of file found; missing ';'."); if (token == ";" || ((token == "," || token == "}") && !nDepth)) break; log << " " << static_cast(token); if (token == "{") nDepth++; if (token == "}") nDepth--; m_lstAssignmentTokenList.push_back(token); } log << std::endl; } else { // Does the entity need an assignment? if (RequiresAssignment()) throw CCompileException(token, "Expecting an assignment operator."); } // Assign any succeeding comments ProcessPostCommentTokenList(token.GetLine()); // Another declaration? if (token == ",") { if (!SupportMultipleDeclarations()) throw CCompileException(token, "Multiple declarations on a single line of code is not supported for this type."); log << "Another declaration of the same type..." << std::endl; // Peek for ending the definition if (DoNotEnfoceNextDeclarationAfterComma() && PeekToken() == "}") return; // Create another declaration CDeclarationEntity* pTypeEntity = nullptr; if (Get()) pTypeEntity = CreateChild(token.GetContext(), GetParentEntity().get(), IsReadOnly(), false)->Get(); else if (Get()) pTypeEntity = CreateChild(token.GetContext(), GetParentEntity().get())->Get(); else if (Get()) pTypeEntity = CreateChild(token.GetContext(), GetParentEntity().get(), IsReadOnly())->Get(); else throw CCompileException(token, "Unexpected token ','."); if (!pTypeEntity) throw CCompileException(token, "Internal error: failed to create another declaration entity."); // Set the new begin position of the declaration CToken tokenTemp = PeekToken(); pTypeEntity->SetBeginPosition(tokenTemp.GetLine(), tokenTemp.GetCol()); // Use the same type for the processing. pTypeEntity->ProcessDeclaration(m_typedecl); // Done. return; } // Set the end position of the declaration SetEndPosition(token.GetLine(), token.GetCol()); // Reinsert the token PrependToken(token); } void CDeclarationEntity::PreprocessArrayDeclaration() { CLog log("Checking for array..."); // For each array dimension, add a tokenlist to the m_vecMultiArraySizeTokenList variable. bool bIsArray = false; while (true) { // Check for an array CToken token = GetToken(); if (token != "[") { PrependToken(token); break; } if (!SupportArrays()) throw CCompileException(token, "Unexpected token '['."); log << "Array detected; storing expression for later processing..." << std::endl; log << "Array expression: ["; // Check for multidimensional arrays if (bIsArray && !GetParserRef().GetEnvironment().MultiDimArrayExtension()) throw CCompileException(token, "Multi-dimentsional arrays are not allowed. Unexpected token '['."); bIsArray = true; // Read the tokens for the array size. Read until ']'. CTokenList lstArraySize; size_t nDepth = 1; while (true) { token = GetToken(); log << " " << static_cast(token); if (!token) throw CCompileException("Unexpected end of file found; missing ']'."); if (token == ";") throw CCompileException("Unexpected end of declaration; missing ']'."); if (token == "[") nDepth++; if (token == "]") { nDepth--; if (!nDepth) { m_vecMultiArraySizeTokenList.push_back(std::move(lstArraySize)); break; } } lstArraySize.push_back(token); } log << std::endl; } } void CDeclarationEntity::PreprocessTokenListVector(std::vector& rvecTokenList) { CLog log("Checking for comma separated token lists..."); CTokenList lstTokens; bool bInitial = true; while (true) { // Check for array index (allowed in certain situations) CToken token = GetToken(); if (token == "[") { do { lstTokens.push_back(token); token = GetToken(); } while (token && token != "]"); lstTokens.push_back(token); token = GetToken(); } // Check for template parameters if (token == "<") { size_t nDepth = 0; do { if (token == "<") nDepth++; if (token == ">>") // Special case when closing nested templates. { token = CToken(">", ETokenType::token_operator); PrependToken(token); } if (token == ">") nDepth--; lstTokens.push_back(token); token = GetToken(); } while (token && static_cast(nDepth)); lstTokens.push_back(token); token = GetToken(); } // Check for end of processing if (!token || token == "]" || token == ")" || token == ";") { if (!bInitial) rvecTokenList.push_back(std::move(lstTokens)); PrependToken(token); break; } if (bInitial) log << "Comma separated list detected: "; bInitial = false; log << " " << static_cast(token); // Check for comma separator if (token == ",") { rvecTokenList.push_back(std::move(lstTokens)); continue; } // Add the token to the token list lstTokens.push_back(token); } if (rvecTokenList.empty()) log << "No comma separated list detected..." << std::endl; else log << std::endl; } void CDeclarationEntity::PostProcess() { CLog log("Post process declaration..."); // Check the assignment processing progression state... switch (m_eProcAssState) { case EProcessAssignmentProgression::unprocessed: // Not processed yet, start processing... m_eProcAssState = EProcessAssignmentProgression::currently_processing; break; case EProcessAssignmentProgression::currently_processing: log << "Post processing declaration assignment takes place already; cannot do this more than once at the same time..." << std::endl; // Alarm, circular references... cannot continue. throw CCompileException("Circular referencing entity."); case EProcessAssignmentProgression::processed: default: // Already done... log << "Post processing declaration was done before; no need to repeat..." << std::endl; return; } // The parent value is the value of the parent entity, if there is any value. CValueNodePtr ptrValueParent = GetParentEntity() ? GetParentEntity()->ValueRef() : nullptr; if (ptrValueParent) log << "The parent entity '" << GetParentEntity()->GetName() << "' has a value node..." << std::endl; else log << "No parent entity or no value node assigned to the parent entity..." << std::endl; // Create a value at each bottom leaf of a multi-dimensional array. std::function)> fnCreateAtBottomLeaf = [&](CValueNodePtr& rptrValue, const CValueNodePtr& rptrValueParent, std::function fnCreate) { // Check for an array value CArrayValueNode* psArrayValue = dynamic_cast(rptrValue.get()); // If there is an array value, call the function once more for each leaf. if (psArrayValue) { for (size_t nIndex = 0; nIndex < psArrayValue->GetSize(); nIndex++) fnCreateAtBottomLeaf((*psArrayValue)[nIndex], rptrValue, fnCreate); return; } // Create a new element... rptrValue = fnCreate(rptrValueParent); }; // Process parameters - since parameters might depend on other parameters, do the processing in two steps. size_t nIndex = 1; for (const CTokenList& rlstTokenList : m_vecParametersTokenList) { log << "Processing parameter #" << nIndex++ << std::endl; CEntityPtr ptrEntity = std::make_shared(GetContext(), shared_from_this(), rlstTokenList); ptrEntity->Process(); m_vecParameters.push_back(ptrEntity); } for (CEntityPtr& rptrParameter : m_vecParameters) rptrParameter->Get()->PostProcess(); // Build array tree if (m_vecMultiArraySizeTokenList.empty()) log << "No array processing needed..." << std::endl; else if (m_vecMultiArraySizeTokenList.size() == 1) log << "Single-dimensional array processing needed..." << std::endl; else log << "Multi-dimensional array processing needed..." << std::endl; for (const CTokenList& rlstArrayExpression : m_vecMultiArraySizeTokenList) { std::pair prArraySize = {0, false}; log << "Start processing array dimension..." << std::endl; // Empty expression indicates retrieving the size from the assignment. if (!rlstArrayExpression.empty()) { log << "Calculate the array size..." << std::endl; prArraySize = ProcessNumericExpression(rlstArrayExpression); // Is the array size dynamic? if (prArraySize.second) throw CCompileException(*rlstArrayExpression.begin(), "Cannot use non-const variable for the array size."); log << "The array has " << prArraySize.first.Get() << " elements..." << std::endl; // Check whether the size is integral if (!prArraySize.first.IsIntegral()) throw CCompileException(*rlstArrayExpression.begin(), "Only integral data types are supported for the array size."); if ((prArraySize.first < CConstVariant(0)).Get()) throw CCompileException(*rlstArrayExpression.begin(), "Invalid array size."); } else { log << "Array is defined as unbound array retrieving the size from the variable assignment..." << std::endl; // Unbound arrays are not possible for writable variables. Exception are operations, attributes and parameters of local // interfaces. bool bError = true; switch (GetType()) { case sdv::idl::EEntityType::type_typedef: bError = false; break; case sdv::idl::EEntityType::type_variable: // When not read-only, this is an error. if (IsReadOnly()) bError = false; break; default: break; } if (bError) throw CCompileException("Retrieving the size of the array through its assignment is" " only possible with const declarations and typedefs."); } // The array creation function auto fnCreateArray = [&, this](const CValueNodePtr& rptrValueParent) -> CValueNodePtr { std::shared_ptr ptrArrayValue = std::make_shared(shared_from_this(), rptrValueParent); if (rlstArrayExpression.empty()) ptrArrayValue->SetFixedSizeUnbound(); else if (prArraySize.second) ptrArrayValue->SetDynamicSize(prArraySize.first.Get(), rlstArrayExpression); else ptrArrayValue->SetFixedSize(prArraySize.first.Get(), rlstArrayExpression); return ptrArrayValue; }; // Create the array value at the bottom leaf. fnCreateAtBottomLeaf(ValueRef(), ptrValueParent, fnCreateArray); log << "Finalized processing array dimension..." << std::endl; } // Add the values of the type. if (m_vecMultiArraySizeTokenList.size() == 0) log << "Copy the existing type value tree or create a declaration value node for this assignment..." << std::endl; else log << "For each array element, copy the existing type value tree or create a" " declaration value node for this assignment..." << std::endl; auto fnCreateTypeValues = [&, this](const CValueNodePtr& rptrValueParent) -> CValueNodePtr { if (m_typedecl.GetTypeDefinitionEntityPtr()) { // In case the original type was not processed yet, do so now. CDeclarationEntity* pOriginalType = m_typedecl.GetTypeDefinitionEntityPtr()->Get(); if (pOriginalType) pOriginalType->PostProcess(); // Copy the existing entity of the type... this contains all the default assignments already... log << "Copy type value tree..." << std::endl; CValueNodePtr ptrValue = m_typedecl.GetTypeDefinitionEntityPtr()->ValueRef(); if (ptrValue) return ptrValue->CreateCopy(shared_from_this(), rptrValueParent); else { if (pOriginalType) throw CCompileException("Internal error: value tree must be available for '", GetName(), "'."); log << "No value tree present; nothing to copy..." << std::endl; return nullptr; } } else { log << "Create declaration value node..." << std::endl; return std::make_shared(shared_from_this(), ptrValueParent); } }; fnCreateAtBottomLeaf(ValueRef(), ptrValueParent, fnCreateTypeValues); // If this is a variable declaration, add the value as part of the parent tree if (CanSupportComplexTypeAssignments() && ptrValueParent) { log << "The entity value tree is part of the value tree of the parent node..." << std::endl; ptrValueParent->AddChild(ValueRef()); } // Add the assignment. if (!m_lstAssignmentTokenList.empty()) { log << "Assignment was available, process the assignment..." << std::endl; if (!SupportAssignments()) throw CCompileException(*m_lstAssignmentTokenList.begin(), "Type definitions cannot be assigned any values."); ValueRef()->ProcessValueAssignment(m_lstAssignmentTokenList); } else { // Does the entity need an assignment? if (RequiresAssignment()) throw CCompileException("Expecting an assignment operator for '", GetName(), "'."); } // Build raises exception lists for (const CTokenList& rlstTokenList : m_vecRaisesExceptionsTokenList) { log << "Processing raising exception..." << std::endl; std::pair prBase = ProcessScopedName(rlstTokenList); if (prBase.first.empty() || !prBase.second || !prBase.second->Get()) throw CCompileException("Exception definition not found."); if (SupportSeparateSetGetRaiseExceptions()) { m_vecGetRaisesExceptions.push_back(prBase.second); m_vecSetRaisesExceptions.push_back(prBase.second); } else m_vecRaisesExceptions.push_back(prBase.second); log << "Entity could raise exception on operation/attribute: " << prBase.first << std::endl; } for (const CTokenList& rlstTokenList : m_vecSetRaisesExceptionsTokenList) { log << "Processing set-raising exception..." << std::endl; std::pair prBase = ProcessScopedName(rlstTokenList); if (prBase.first.empty() || !prBase.second || !prBase.second->Get()) throw CCompileException("Exception definition not found."); m_vecSetRaisesExceptions.push_back(prBase.second); log << "Entity could raise exception on set attribute: " << prBase.first << std::endl; } for (const CTokenList& rlstTokenList : m_vecGetRaisesExceptionsTokenList) { log << "Processing get-raising exception..." << std::endl; std::pair prBase = ProcessScopedName(rlstTokenList); if (prBase.first.empty() || !prBase.second || !prBase.second->Get()) throw CCompileException("Exception definition not found."); m_vecGetRaisesExceptions.push_back(prBase.second); log << "Entity could raise exception on get attribute: " << prBase.first << std::endl; } // Processing is finalized... m_eProcAssState = EProcessAssignmentProgression::processed; } bool CDeclarationEntity::RequiresAssignment() const { if (!ValueRef()) return false; // No value assigned yet... // If the type has an unbound array in its value, it requires assignment to determine the size of the type. CValueNodePtr ptrValue = ValueRef(); while (ptrValue) { if (ptrValue->IsArray() && ptrValue->IsUnbound()) return true; ptrValue = ptrValue->GetParentNode(); } return false; } void CDeclarationEntity::CalcHash(CHashObject& rHash) const { // Add the type if (m_typedecl.GetTypeDefinitionEntityPtr()) m_typedecl.GetTypeDefinitionEntityPtr()->CalcHash(rHash); else rHash << m_typedecl.GetTypeString(); // Add base entity CEntity::CalcHash(rHash); // Add array dimensions sdv::sequence seqArray = GetArrayDimensions(); for (const sdv::idl::SArrayDimension& rsDimentation : seqArray) { rHash << "["; if (!rsDimentation.ssExpression.empty()) rHash << rsDimentation.ssExpression; rHash << "]"; } // Get the assignment std::string ssAssignment = GetAssignment(); if (!ssAssignment.empty()) rHash << ssAssignment; // Add parameters for (const CEntityPtr& rptrEntity : m_vecParameters) rptrEntity->CalcHash(rHash); // Add exceptions for (const CEntityPtr& rptrEntity : m_vecRaisesExceptions) { rHash << "raises"; rptrEntity->CalcHash(rHash); } // Add get-exceptions for (const CEntityPtr& rptrEntity : m_vecGetRaisesExceptions) { rHash << "get_raises"; rptrEntity->CalcHash(rHash); } // Add set-exceptions for (const CEntityPtr& rptrEntity : m_vecSetRaisesExceptions) { rHash << "set_raises"; rptrEntity->CalcHash(rHash); } // Add whether it is readonly if (IsReadOnly()) rHash << "const"; }