/******************************************************************************** * 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 "parser.h" #include "exception.h" CParser::CParser(const char* szCode, const CIdlCompilerEnvironment& renv /*= CIdlCompilerEnvironment()*/) : CPreprocessor(*this), m_lexer(this, renv.CaseSensitiveTypeExtension()), m_environment(renv) { // Create soure code context object CContextPtr ptrContext = std::make_shared(szCode); // Add additional keywords to the lexer. if (renv.InterfaceTypeExtension()) { m_lexer.AddKeyword("interface_id"); m_lexer.AddKeyword("interface_t"); m_lexer.AddKeyword("null"); } if (renv.ExceptionTypeExtension()) { m_lexer.AddKeyword("exception_id"); } if (renv.PointerTypeExtension()) { m_lexer.AddKeyword("pointer"); } if (renv.UnicodeExtension()) { m_lexer.AddKeyword("char16"); m_lexer.AddKeyword("char32"); m_lexer.AddKeyword("u8string"); m_lexer.AddKeyword("u16string"); m_lexer.AddKeyword("u32string"); } // Parse the source ParserPrepare(ptrContext); } CParser::CParser(const std::filesystem::path& rpath, const CIdlCompilerEnvironment& renv /*= CIdlCompilerEnvironment()*/) : CPreprocessor(*this), m_lexer(this, renv.CaseSensitiveTypeExtension()), m_environment(renv), m_pathFile(rpath) { // Create soure code context object CContextPtr ptrContext = std::make_shared(rpath); // Add additional keywords to the lexer. if (renv.InterfaceTypeExtension()) { m_lexer.AddKeyword("interface_id"); m_lexer.AddKeyword("interface_t"); m_lexer.AddKeyword("null"); } if (renv.ExceptionTypeExtension()) { m_lexer.AddKeyword("exception_id"); } if (renv.PointerTypeExtension()) { m_lexer.AddKeyword("pointer"); } if (renv.UnicodeExtension()) { m_lexer.AddKeyword("char16"); m_lexer.AddKeyword("char32"); m_lexer.AddKeyword("u8string"); m_lexer.AddKeyword("u16string"); m_lexer.AddKeyword("u32string"); } // Parse the source ParserPrepare(ptrContext); } sdv::interface_t CParser::GetInterface(sdv::interface_id idInterface) { // Own interfaces first if (idInterface == sdv::GetInterfaceId()) return static_cast(this); if (idInterface == sdv::GetInterfaceId()) return static_cast(this); // Then ask the environment sdv::interface_t ifc = m_environment.GetInterface(idInterface); // Then as the root if (ifc == nullptr && m_ptrRoot != nullptr) ifc = m_ptrRoot->GetInterface(idInterface); return ifc; } sdv::u8string CParser::GetFilePath() const { return m_pathFile.u8string(); } sdv::u8string CParser::GetOutputDir() const { return m_environment.GetOutputDir().generic_u8string(); } const CLexer& CParser::GetLexer() const { return m_lexer; } void CParser::IncludeFile(const std::filesystem::path& rpath, bool bLocal) { // Create soure code context object CContextPtr ptrContext = std::make_shared(rpath, bLocal ? sdv::idl::IEntityContext::ELocation::local_include : sdv::idl::IEntityContext::ELocation::global_include); // Prepare for parsing the file ParserPrepare(ptrContext); } CIdlCompilerEnvironment& CParser::GetEnvironment() { return m_environment; } CToken CParser::GetToken() { // Check the token cache. if (!m_lstCacheTokenList.empty()) { CToken token = m_lstCacheTokenList.front(); m_lstCacheTokenList.pop_front(); return token; } // No token in the cache. Get the token from the lexer. CToken token = GetTokenFromLexer(false); return token; } CToken CParser::GetLastValidToken() const { return m_lexer.GetLastValidToken(); } CToken CParser::PeekToken(size_t nIndex /*= 0*/) { CToken token; // Check the token cache. If not large enough, fill it up. while (m_lstCacheTokenList.size() <= nIndex) { token = GetTokenFromLexer(true); if (!token) return token; m_lstCacheTokenList.push_back(token); } CTokenList::iterator itPos = m_lstCacheTokenList.begin(); for (size_t nCnt = 0; nCnt < nIndex; ++nCnt) ++itPos; token = *itPos; return token; } CTokenList CParser::GetComments() { // If there are no comments, request the next token from the lexer (peeking only); this will request comments. if (m_lstComments.empty()) PeekToken(); return m_lstComments; } void CParser::PrependToken(const CToken& rToken) { // Add the token at the front of to the cache list if (!rToken) return; m_lstCacheTokenList.push_front(rToken); } void CParser::SkipAdjacentComments() { PeekToken(); uint32_t uiLine = 0; while (!m_lstComments.empty()) { const CToken token = m_lstComments.front(); if (!uiLine || token.GetLine() <= uiLine + 1) { uiLine = token.GetLine(); m_lstComments.pop_front(); } else break; } } CParser& CParser::LexicalCheck() { while (GetToken()); return *this; } CParser& CParser::Parse() { // Stack is empty if there is no code available (or everything was parsed already). This is not an error. if (m_stackCode.empty()) return *this; // Create the root entity. m_ptrRoot = std::make_shared(*this, m_stackCode.top()); CRootEntity* pRoot = m_ptrRoot->Get(); if (!pRoot) throw CCompileException("Internal error: cannot get access to the root object."); try { // Parse the root entity. pRoot->Process(); } catch (sdv::idl::XCompileError& rexcept) { // Use the class for easy access. CCompileException exception(rexcept); // Add the line number if not existing if (!exception.GetLineNo()) exception.SetLocation(m_lexer.GetLastValidToken()); // Add the path of the code causing the exception to the exception. if (exception.GetPath().empty()) exception.SetPath(m_stackCode.empty() ? m_pathFile : m_stackCode.top()->Source().GetPathRef()); // Assign the changed values. Needed to keep the additional information that the derived class might have. rexcept = exception; // Rethrow the original derived class. throw; } return *this; } const CRootEntity* CParser::Root() const { return m_ptrRoot ? m_ptrRoot->Get() : nullptr; } std::string CParser::GenerateAnonymousEntityName(const std::string& rssPrefix) { // Find the prefix in the map auto itPrefix = m_mapAutoNameCount.find(rssPrefix); if (itPrefix == m_mapAutoNameCount.end()) // Prefix not know; add a new prefix itPrefix = m_mapAutoNameCount.emplace(rssPrefix, 0).first; return rssPrefix + "_" + std::to_string(itPrefix->second++); } std::list CParser::GetAndRemoveMeta() { return std::move(m_lstMeta); } void CParser::ParserPrepare(CContextPtr& rptrContext) { // Check whether the file was already added to the parser (do not include the same file multipe times). if (m_setProcessedFiles.find(rptrContext->Source().GetPathRef()) != m_setProcessedFiles.end()) return; m_setProcessedFiles.insert(rptrContext->Source().GetPathRef()); // Check for maximum parsing depth. if (m_stackCode.size() >= m_nMaxDepth) throw CCompileException("Passed maximum amount of nested files - circular inclusion of files?"); // Add the source code context to the stack m_stackCode.push(rptrContext); // A new file will be processed. Enable preprocessing on the lexer. m_lexer.EnablePreprocProcessing(); } void CParser::InsertWhitespace(const CToken&) {} void CParser::InsertComment(const CToken& rtoken) { // Assign the context to the token (this prevents the context from unloading when the token is still in use). CToken tokenComment = rtoken; tokenComment.SetContext(m_stackCode.top()); // Comments can be invalid after the next token is read (in case the next token is in another file). Cache the comment // text by requesting the string. m_lstComments.push_back(std::move(tokenComment)); } void CParser::ProcessPreprocDirective(CCodePos& rCode) { // Get the current parse context CContextPtr ptrContext = m_stackCode.top(); // Callback inserting preprocessor directives CToken tokenMeta = ProcessPreproc(rCode, ptrContext); if (tokenMeta) { // Check for adjacent preceding comments uint32_t uiLine = tokenMeta.GetLine(); CTokenList lstMetaComments; while (m_lstComments.size()) { uint32_t uiCommentLine = m_lstComments.back().GetEndLine(); if (uiCommentLine < uiLine - 1) break; // Not connecting to the meta data if (uiCommentLine > uiLine) break; // Not preceding the meta data uiLine = uiCommentLine; lstMetaComments.push_front(m_lstComments.back()); m_lstComments.pop_back(); } m_lstMeta.push_back(SMetaToken{ tokenMeta, lstMetaComments }); } } CToken CParser::GetTokenFromLexer(bool /*bPeekOnly*/) { // Clear the comments m_lstComments.clear(); // Check for code if (m_stackCode.empty()) return CToken(); // Get the token from the code CToken token; try { // Get a token as long as no token was found or the stack while (!m_stackCode.empty()) { CCodePos &rCode = m_stackCode.top()->Code(); // If the current position is not part of the macro expansion, reset the set of macros used in a expansion. bool bInMacroExpansion = rCode.CurrentPositionInMacroExpansion(); // Get a token. This will automatically trigger preprocessing control. // Assign the context to the token (this prevents the context from unloading when the token is still in use). token = m_lexer.GetToken(rCode, m_stackCode.top()); // Should this code be processed. If not, skip a line and try again... if (!CurrentSectionEnabled()) { // Check for EOF if (token) // Not EOF m_lexer.SkipLine(rCode); else // EOF, process the next file { // Check for EOF. If so, close the file. // ATTENTION Do not use rCode, since the stack might have changed through a preprocessor include directive. if (m_stackCode.top()->Code().HasEOF()) { // Trigger final preproc processing (to check for correct conditional inclusion boundaries). FinalProcessing(m_stackCode.top()->Code()); // Remove the top file from the stack m_stackCode.pop(); } } continue; } // Check whether the token is an identifier, if so, check for any macro if (token.GetType() == ETokenType::token_identifier || token.GetType() == ETokenType::token_keyword) { if (GetEnvironment().TestAndExpand(static_cast(token).c_str(), rCode, bInMacroExpansion)) continue; // macro was replaced, get a token again. } // Return the token if one was returned. if (token) return token; // Check for EOF. If so, close the file. // ATTENTION Do not use rCode, since the stack might have changed through a preprocessor include directive. if (!m_stackCode.empty() && m_stackCode.top()->Code().HasEOF()) { // Trigger final preproc processing (to check for correct conditional inclusion boundaries). FinalProcessing(m_stackCode.top()->Code()); // Remove the top file from the stack m_stackCode.pop(); } } } catch (sdv::idl::XCompileError& rexcept) { // Use the class for easy access. CCompileException exception(rexcept); // Add the token to the code if no token was assigned. if (token) exception.SetLocation(token); // Add the path of the code causing the exception to the exception. exception.SetPath(m_stackCode.top()->Source().GetPathRef()); // Assign the changed values. Needed to keep the additional information that the derived class might have. rexcept = exception; // Rethrow the original derived class. throw; } // At this point, all files were processed. There is no more token. return CToken(); }