#pragma once #include #include #include #include #include #include #include #include #include #ifdef __GNUC__ // Some GCC compilers return an overflow warning on string_view functions that use npos as a parameter. This is a known issue // described here: // https://stackoverflow.com/questions/79738323/gcc-warning-memcpy-specified-bound-18446744073709551614-exceeds-maximum-objec // Suppress the warning. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-overflow" #pragma GCC diagnostic ignored "-Walloc-size-larger-than=" #pragma GCC diagnostic ignored "-Wstringop-overread" #endif /** * @brief Simple TOML parser to read keys and tables. This TOML parser can be used to parse a TOML string without the use of the * SDV framework (e.g. before system startup). */ namespace sdv::toml::simple_parser { /** * @brief Enumeration for all supported TOML node types */ enum class ENodeType { node_unknown, ///< Node type not valid. node_table, ///< Node represents a map of key-value pairs node_array, ///< Node represents a list of elements node_value ///< Node represents a scalar value (string, number, boolean, etc.) }; // Forward declaration of the main data structure struct SNode; /// Table map alias using TTableMap = std::map; /// Array vector alias using TNodeArray = std::vector; /** * @brief Main variant structure holding the configuration tree data */ struct SNode { ENodeType eType = ENodeType::node_unknown; ///< Enumeration Type std::string ssKey; ///< Name of the value key std::string ssValueRaw = ""; ///< String raw value TTableMap mapTable = {}; ///< Map Table TNodeArray vecArray = {}; ///< Vector Array bool bIsInline = false; ///< Locked if defined via inline {...} syntax bool bIsExplicitlyHeadered = false; ///< Set to true when defined via [header] syntax /** * @brief Helper checking if node is valid. * @return Returns whether the node is valid (is not unknown). */ operator bool() const noexcept { return eType != ENodeType::node_unknown; } /** * @brief Helper checking if node is valid. * @return Returns whether the node is valid (is not unknown). */ bool IsValid() const noexcept { return eType != ENodeType::node_unknown; } /** * @brief Helper checking if node is a table. * @return Returns whether the node is a table. */ bool IsTable() const noexcept { return eType == ENodeType::node_table; } /** * @brief Helper checking if node is an array. * @return Returns whether the node is an array. */ bool IsArray() const noexcept { return eType == ENodeType::node_array; } /** * @brief Helper checking if node is a scalar value. * @return Returns whether the node is a value node. */ bool IsValue() const noexcept { return eType == ENodeType::node_value; } /** * @brief Access function returning the type. * @return The type of the node. */ ENodeType GetType() const noexcept { return eType; } /** * @brief Access function returning the key name. * @return Reference to the string containing the key name (if not an array member) or an empty string when there is no * key name. */ const std::string& GetName() const noexcept { return ssKey; } /** * @brief Returns the raw value. * @return Reference to the string containing the raw value of the node. Or returns an empty string when the node is not * a value node or doesn't contain a value. */ const std::string& GetValue() const noexcept { return ssValueRaw; } /** * @brief Return the value converted to the provided type. * @tparam TType Type to convert to. Supported are integral types, bool, floating point types and std::string. * @return The result from the conversion or an empty result if the conversion could not be done. */ template TType GetValue() const { if constexpr (std::is_same_v) { return ssValueRaw == "true" || GetValue() != 0; } else if constexpr (std::is_integral_v || std::is_floating_point_v) { TType tValue{}; auto [ptr, ec] = std::from_chars(ssValueRaw.data(), ssValueRaw.data() + ssValueRaw.size(), tValue); return ec == std::errc() ? tValue : TType{}; } else if (std::is_same_v) { return ssValueRaw; } else return {}; } /** * @brief Returns the table map. * @return If the node is a table, returns a reference to the table map. Or returns an empty map if the node is not a table * or doesn't contain child nodes. */ const TTableMap& GetTable() const noexcept { return mapTable; } /** * @brief Return the array vector. * @return If the node is an array, returns a reference to the array vector. Or returns an empty vector if the node is not * an array or doesn't contain any elements. */ const TNodeArray& GetArray() const noexcept { return vecArray; } /** * @brief Safe, non-recursive direct lookup using dot and array notation (e.g., "table.array[1].key") * @param[in] svPath String containing the value path to look for. * @return Returns a safe, read-only reference to the node of an empty node when not found. */ const SNode GetDirect(std::string_view svPath) const noexcept { static const SNode sNodeEmpty; std::reference_wrapper refCurrentNode = *this; size_t nStart = 0; // Index counter for path segments while (nStart < svPath.size()) { // 1. Find the next path segment up to the dot delimiter size_t nDot = svPath.find('.', nStart); std::string_view svPart = svPath.substr(nStart, nDot == std::string_view::npos ? nDot : nDot - nStart); if (svPart.empty()) return sNodeEmpty; // 2. Check if the segment contains array brackets [...] (e.g., "arr_mixed[4]") size_t nOpenBracket = svPart.find('['); std::string ssKeyLocal(svPart.substr(0, nOpenBracket)); // Must be a table node to perform a key lookup if (!refCurrentNode.get().IsTable()) return sNodeEmpty; auto it = refCurrentNode.get().mapTable.find(ssKeyLocal); if (it == refCurrentNode.get().mapTable.end()) return sNodeEmpty; refCurrentNode = std::cref(it->second); // 3. Resolve array indices sequentially (supports multidimensional arrays or inline tables in arrays) size_t nBracketPos = nOpenBracket; while (nBracketPos != std::string_view::npos) { size_t nCloseBracket = svPart.find(']', nBracketPos); if (nCloseBracket == std::string_view::npos) return sNodeEmpty; // Error: Malformed unclosed bracket // Extract and parse the index string substring std::string_view svIndex = svPart.substr(nBracketPos + 1, nCloseBracket - nBracketPos - 1); // Ensure the index consists strictly of digits (prevents negative signs or alpha characters) if (svIndex.empty() || !std::all_of(svIndex.begin(), svIndex.end(), [](unsigned char c) { return std::isdigit(c); })) return sNodeEmpty; size_t nIdx = 0; try { nIdx = static_cast(std::stoull(std::string(svIndex))); } catch (...) { return sNodeEmpty; // Safety catch against integer overflow attacks during parsing } // SNode type verification and strict bounds check (Safety-critical constraint) if (!refCurrentNode.get().IsArray() || nIdx >= refCurrentNode.get().vecArray.size()) return sNodeEmpty; // Advance target reference directly into the array index element refCurrentNode = std::cref(refCurrentNode.get().vecArray[nIdx]); // Check for a consecutive opening bracket immediately following (multidimensional arrays) nBracketPos = svPart.find('[', nCloseBracket); } // Step over the dot delimiter to move onto the next token sequence if (nDot == std::string_view::npos) break; nStart = nDot + 1; } return refCurrentNode; } }; /** * @brief Main processing unit containing the parsing logic */ class CParser { public: /** * @brief Explicit constructor initializing the parser target view bounds * @param[in] svInput The TOML string. */ // cppcheck-suppress passedByValue CParser(std::string_view svInput) : m_svSrc(svInput), m_nPos(0) { Parse(); } /** * @brief Get the root node. * @return Return a reference to the root node. */ const SNode& Root() const noexcept { return m_sRoot; } private: std::string_view m_svSrc; ///< Member String View size_t m_nPos; ///< Member Numerical index) SNode m_sRoot; ///< Member Object structure /// Safety Extension: Stores dot-joined path strings of tables defined explicitly via [headers] std::vector m_vecExplicitlyDefinedTables; /** * @brief Executes the lexical analysis and builds the root node tree * @return The root node. */ SNode Parse() { m_sRoot.eType = ENodeType::node_table; std::vector vCurrentTablePath; while (!IsEof()) { // Only skip inline whitespace and comments, preserve newlines to check line boundaries while (!IsEof()) { char c = Peek(); if (c == ' ' || c == '\t' || c == '\r') { Consume(); } else if (c == '#') { while (!IsEof() && Peek() != '\n' && Peek() != '\r') Consume(); } else { break; } } if (IsEof()) break; char cNext = Peek(); if (cNext == '\n') { Consume(); // Valid empty line separation continue; } if (cNext == '[') { vCurrentTablePath = ParseTableHeader(); } else { SNode& oActiveTable = NavigateToTable(vCurrentTablePath); ParseKeyValue(oActiveTable); } // Safety Constraint: A key-value assignment or header MUST be followed immediately // by a newline, a comment, or the end of the file. No trailing gibberish allowed on the same line. while (!IsEof() && (Peek() == ' ' || Peek() == '\t')) Consume(); if (!IsEof() && Peek() == '#') { while (!IsEof() && Peek() != '\n' && Peek() != '\r') Consume(); } if (!IsEof() && Peek() != '\n' && Peek() != '\r') { throw std::runtime_error("Multiple declarations on a single line are strictly invalid in TOML."); } } return m_sRoot; } /** * @brief Internal safety helper navigating down a string path to return a mutable reference. Automatically diverts into the * newest element block if tracking a structural Table Array. * @param[in] rvecPath Reference to vector containing the path * @return Reference to the node represented by the path. */ SNode& NavigateToTable(const std::vector& rvecPath) { std::reference_wrapper refCurr = m_sRoot; for (const auto& rssSection : rvecPath) { // Safe traversal step into the standard node map structure refCurr = std::ref(refCurr.get().mapTable[rssSection]); // Context Redirect: If target is a Table Array, dive into the last active table instance if (refCurr.get().IsArray() && !refCurr.get().vecArray.empty()) refCurr = std::ref(refCurr.get().vecArray.back()); } return refCurr.get(); } /** * @brief Safety checks ensuring operations remain within string limits * @return Returns whether the current position exceeds the string size. */ bool IsEof() const noexcept { return m_nPos >= m_svSrc.size(); } /** * @brief Non-destructive parsing look-ahead function * @return The character at the position or a null-character if EOF. */ char Peek() const noexcept { return IsEof() ? '\0' : m_svSrc[m_nPos]; } /** * @brief Advances processing cursor forward safely * @return The character at the position or a null-character if EOF. */ char Consume() noexcept { return IsEof() ? '\0' : m_svSrc[m_nPos++]; } /** * @brief Skips standard spacing gaps, multi-line format updates, and hash strings */ void SkipWhitespaceAndComments() noexcept { while (!IsEof()) { char c = Peek(); if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { Consume(); } else if (c == '#') { while (!IsEof() && Peek() != '\n' && Peek() != '\r') { Consume(); } } else { break; } } } /** * @brief Skips single inline whitespaces (spaces and tabs) */ void SkipInlineWhitespace() noexcept { while (!IsEof() && (Peek() == ' ' || Peek() == '\t')) Consume(); } /** * @brief Evaluates complex dot-separated key sequences safely * @return Returns a vector containing the dotted key parts. */ std::vector ParseDottedKey() { std::vector vecKeys; // Vector of strings while (!IsEof()) { SkipInlineWhitespace(); std::string ssKey = ""; // String token char c = Peek(); if (c == '"' || c == '\'') ssKey = ParseStringNode(); else { while (!IsEof()) { char bc = Peek(); // Bare Char if (std::isalnum(static_cast(bc)) || bc == '-' || bc == '_') { ssKey += Consume(); } else { break; } } if (ssKey.empty()) throw std::runtime_error("Empty or invalid key sequence."); } vecKeys.push_back(ssKey); SkipInlineWhitespace(); if (Peek() == '.') { Consume(); } else { break; } } return vecKeys; } /** * @brief Handles text string isolation and processes updated escape arrays * @return The string after parsing. */ std::string ParseStringNode() { char cQuote = Consume(); // Character delimiter bool bIsMultiline = false; // Boolean flag if (Peek() == cQuote) { Consume(); // Warning of cppcheck for the condition to be always true. This is not the case due to the Consume function // execution. Suppress warning. // cppcheck-suppress knownConditionTrueFalse if (Peek() == cQuote) { Consume(); bIsMultiline = true; } else { return ""; } } std::string ssResult = ""; while (!IsEof()) { if (bIsMultiline && Peek() == cQuote) { if (m_nPos + 2 < m_svSrc.size() && m_svSrc[m_nPos + 1] == cQuote && m_svSrc[m_nPos + 2] == cQuote) { m_nPos += 3; return ssResult; } } else if (!bIsMultiline && Peek() == cQuote) { Consume(); return ssResult; } char c = Consume(); if (cQuote == '"' && c == '\\') { if (IsEof()) throw std::runtime_error("Unfinished escape sequence."); char cEsc = Consume(); // Character escape sequence identifier if (cEsc == 'n') ssResult += '\n'; else if (cEsc == 't') ssResult += '\t'; else if (cEsc == 'r') ssResult += '\r'; else if (cEsc == '"') ssResult += '"'; else if (cEsc == '\\') ssResult += '\\'; else if (cEsc == 'e') ssResult += '\x1b'; else if (cEsc == 'x') { if (m_nPos + 1 >= m_svSrc.size()) throw std::runtime_error("Truncated hex character."); std::string ssHex{Consume(), Consume()}; ssResult += static_cast(std::stoi(ssHex, nullptr, 16)); } else if (bIsMultiline && (cEsc == '\n' || cEsc == '\r')) { if (cEsc == '\r' && Peek() == '\n') Consume(); SkipInlineWhitespace(); } } else { ssResult += c; } } throw std::runtime_error("Unterminated TOML string sequence detected."); } /** * @brief Evaluates header configurations structural tables or table arrays ([[table.path]]) * @return Returns a vector with table headers. */ //std::vector ParseTableHeader() //{ // Consume(); // Consume standard structural '[' // bool bIsTableArray = false; // if (Peek() == '[') // { // Consume(); // bIsTableArray = true; // } // std::vector vecSections = ParseDottedKey(); // SkipInlineWhitespace(); // if (bIsTableArray) // { // if (Consume() != ']' || Consume() != ']') // throw std::runtime_error("Malformed table array closure."); // } // else // { // if (Consume() != ']') // throw std::runtime_error("Malformed table closure."); // } // // Generate a unified lookup string for this table path (e.g., "fruit.apple") // std::string strFullNormalizedPath = ""; // for (size_t i = 0; i < vecSections.size(); ++i) // { // strFullNormalizedPath += vecSections[i] + (i == vecSections.size() - 1 ? "" : "."); // } // // Safety Constraint: Check if this specific exact table header path was already explicitly declared // if (!bIsTableArray) // { // if (std::find(m_vecExplicitlyDefinedTables.begin(), m_vecExplicitlyDefinedTables.end(), strFullNormalizedPath) // != m_vecExplicitlyDefinedTables.end()) // { // throw std::runtime_error("Duplicate table declaration detected: " + strFullNormalizedPath); // } // m_vecExplicitlyDefinedTables.push_back(strFullNormalizedPath); // } // std::reference_wrapper refCurrent = m_sRoot; // for (size_t i = 0; i < vecSections.size() - 1; ++i) // { // const auto& ssSection = vecSections[i]; // auto it = refCurrent.get().mapTable.find(ssSection); // if (it != refCurrent.get().mapTable.end()) // { // if (it->second.IsValue()) // { // throw std::runtime_error("Cannot re-define a scalar key value as an intermediate table structure."); // } // // If it exists but is a literal array (not explicitly headered), collision! // if (it->second.IsArray() && !it->second.bIsExplicitlyHeadered) // { // throw std::runtime_error("Type collision: Cannot append table elements to a literal array."); // } // } // else // { // SNode sNewNode; // sNewNode.eType = ENodeType::node_table; // sNewNode.ssKey = ssSection; // refCurrent.get().mapTable[ssSection] = sNewNode; // } // refCurrent = std::ref(refCurrent.get().mapTable[ssSection]); // if (refCurrent.get().IsArray() && !refCurrent.get().vecArray.empty()) // { // refCurrent = std::ref(refCurrent.get().vecArray.back()); // } // } // std::string ssFinalSection = vecSections.back(); // auto itFinal = refCurrent.get().mapTable.find(ssFinalSection); // if (itFinal != refCurrent.get().mapTable.end()) // { // if (itFinal->second.IsValue()) // { // throw std::runtime_error("Conflict: Table header overrides an existing scalar value."); // } // if (bIsTableArray && itFinal->second.IsTable()) // { // throw std::runtime_error("Type collision: Static table cannot be turned into a table array."); // } // if (!bIsTableArray && itFinal->second.IsArray()) // { // if (itFinal->second.bIsExplicitlyHeadered) // { // throw std::runtime_error("Type collision: Array of tables cannot be redefined as a standard table."); // } // else // { // throw std::runtime_error("Type collision: Literal array cannot be redefined as a table."); // } // } // // CRITICAL FIX: The invalid exception here was thrown when itFinal->second.IsTable() was true // // but it was implicitly created by a sub-key earlier (e.g., fruit.apple). // // In TOML, a header can safely claim an implicit table, UNLESS it attempts to redefine a value // // that is an explicit scalar or another explicit table type. // } // if (bIsTableArray) // { // if (refCurrent.get().mapTable.find(ssFinalSection) == refCurrent.get().mapTable.end()) // { // SNode sNewArrayNode; // sNewArrayNode.eType = ENodeType::node_array; // sNewArrayNode.ssKey = ssFinalSection; // sNewArrayNode.bIsExplicitlyHeadered = true; // Mark as Table Array type // refCurrent.get().mapTable[ssFinalSection] = sNewArrayNode; // } // SNode sNewTableInstance; // sNewTableInstance.eType = ENodeType::node_table; // sNewTableInstance.ssKey = ssFinalSection; // sNewTableInstance.bIsExplicitlyHeadered = true; // refCurrent.get().mapTable[ssFinalSection].vecArray.push_back(sNewTableInstance); // } // else // { // if (refCurrent.get().mapTable.find(ssFinalSection) == refCurrent.get().mapTable.end()) // { // SNode sNewNode; // sNewNode.eType = ENodeType::node_table; // sNewNode.ssKey = ssFinalSection; // sNewNode.bIsExplicitlyHeadered = true; // refCurrent.get().mapTable[ssFinalSection] = sNewNode; // } // else // { // refCurrent.get().mapTable[ssFinalSection].bIsExplicitlyHeadered = true; // } // } // return vecSections; //} // Evaluates header configurations structural tables or table arrays ([[table.path]]) std::vector ParseTableHeader() { Consume(); // Consume standard structural '[' bool bIsTableArray = false; if (Peek() == '[') { Consume(); bIsTableArray = true; } std::vector vecSections = ParseDottedKey(); SkipInlineWhitespace(); if (bIsTableArray) { if (Consume() != ']' || Consume() != ']') throw std::runtime_error("Malformed table array closure."); } else { if (Consume() != ']') throw std::runtime_error("Malformed table closure."); } std::reference_wrapper refCurrent = m_sRoot; // 1. Traverse down through intermediate namespaces until the second-to-last token component for (size_t i = 0; i < vecSections.size() - 1; ++i) { const auto& ssSection = vecSections[i]; if (refCurrent.get().bIsInline) { throw std::runtime_error("Cannot add subtables to an immutable inline table."); } auto it = refCurrent.get().mapTable.find(ssSection); if (it != refCurrent.get().mapTable.end()) { if (it->second.IsValue()) { throw std::runtime_error("Cannot re-define a scalar key value as an intermediate table structure."); } if (it->second.IsArray() && !it->second.bIsExplicitlyHeadered) { throw std::runtime_error("Type collision: Cannot append table elements to a literal array."); } } else { SNode sNewNode; sNewNode.eType = ENodeType::node_table; sNewNode.ssKey = ssSection; refCurrent.get().mapTable[ssSection] = sNewNode; } // Advance cursor reference refCurrent = std::ref(refCurrent.get().mapTable[ssSection]); // Context Redirect: Always dive into the newest array element block if tracking a structural Table Array if (refCurrent.get().IsArray() && !refCurrent.get().vecArray.empty()) { refCurrent = std::ref(refCurrent.get().vecArray.back()); } } // 2. Handle the final explicit destination node leaf signature std::string ssFinalSection = vecSections.back(); auto itFinal = refCurrent.get().mapTable.find(ssFinalSection); if (itFinal != refCurrent.get().mapTable.end()) { if (itFinal->second.IsValue()) { throw std::runtime_error("Conflict: Table header overrides an existing scalar value."); } if (bIsTableArray && itFinal->second.IsTable()) { throw std::runtime_error("Type collision: Static table cannot be turned into a table array."); } if (!bIsTableArray && itFinal->second.IsArray()) { if (itFinal->second.bIsExplicitlyHeadered) { throw std::runtime_error("Type collision: Array of tables cannot be redefined as a standard table."); } else { throw std::runtime_error("Type collision: Literal array cannot be redefined as a table."); } } // SAFETY CONSTRAINT REMEDIED: // Only throw duplicate declaration errors if a standard table header is redefined // *explicitly* inside the exact same local scope block wrapper. if (!bIsTableArray && itFinal->second.IsTable() && itFinal->second.bIsExplicitlyHeadered) { throw std::runtime_error("Duplicate table declaration detected in this scope: " + ssFinalSection); } } // 3. Construct or instantiate the final target node leaf elements safely if (bIsTableArray) { if (refCurrent.get().mapTable.find(ssFinalSection) == refCurrent.get().mapTable.end()) { SNode sNewArrayNode; sNewArrayNode.eType = ENodeType::node_array; sNewArrayNode.ssKey = ssFinalSection; sNewArrayNode.bIsExplicitlyHeadered = true; refCurrent.get().mapTable[ssFinalSection] = sNewArrayNode; } SNode sNewTableInstance; sNewTableInstance.eType = ENodeType::node_table; sNewTableInstance.ssKey = ssFinalSection; sNewTableInstance.bIsExplicitlyHeadered = true; refCurrent.get().mapTable[ssFinalSection].vecArray.push_back(sNewTableInstance); } else { if (refCurrent.get().mapTable.find(ssFinalSection) == refCurrent.get().mapTable.end()) { SNode sNewNode; sNewNode.eType = ENodeType::node_table; sNewNode.ssKey = ssFinalSection; sNewNode.bIsExplicitlyHeadered = true; refCurrent.get().mapTable[ssFinalSection] = sNewNode; } else { refCurrent.get().mapTable[ssFinalSection].bIsExplicitlyHeadered = true; } } return vecSections; } /** * @brief Extracts key assignment links and creates required target structural tables. Works relative to the passed * rsParentTable node, ensuring proper nesting. * @param[in] rsParentTable Reference to the parent table. */ void ParseKeyValue(SNode& rsParentTable) { std::vector vecKeys = ParseDottedKey(); SkipInlineWhitespace(); if (Consume() != '=') throw std::runtime_error("Missing valid variable declaration assignment mapping."); SkipInlineWhitespace(); std::reference_wrapper refTarget = std::ref(rsParentTable); for (size_t i = 0; i < vecKeys.size() - 1; ++i) { // ONLY block if we are attempting to add elements down through a table that was completed // and locked as an immutable inline structure from an external scope. if (refTarget.get().bIsInline) { throw std::runtime_error("Cannot traverse or append keys through an immutable inline table."); } auto it = refTarget.get().mapTable.find(vecKeys[i]); if (it != refTarget.get().mapTable.end()) { if (it->second.IsValue()) { throw std::runtime_error("Collision: Implied intermediate segment conflicts with an existing scalar."); } if (it->second.bIsInline) { throw std::runtime_error("Collision: Implied path segment penetrates an immutable inline table."); } } else { SNode sNewNode; sNewNode.eType = ENodeType::node_table; sNewNode.ssKey = vecKeys[i]; refTarget.get().mapTable[vecKeys[i]] = sNewNode; } refTarget = std::ref(refTarget.get().mapTable[vecKeys[i]]); if (refTarget.get().IsArray() && !refTarget.get().vecArray.empty()) { refTarget = std::ref(refTarget.get().vecArray.back()); } } std::string ssFinalKey = vecKeys.back(); // Safety Constraint: Check if the final target key value has already been populated in this specific map context if (refTarget.get().mapTable.find(ssFinalKey) != refTarget.get().mapTable.end()) { throw std::runtime_error("Duplicate key configuration definition detected: " + ssFinalKey); } SNode sParsedValue = ParseValueNode(); // Safety Constraint: Catch empty data assignment attempts like `key = # comment` if (sParsedValue.IsValue() && sParsedValue.ssValueRaw.empty()) { throw std::runtime_error("Assignments cannot be empty or contain only comment elements."); } sParsedValue.ssKey = ssFinalKey; refTarget.get().mapTable[ssFinalKey] = sParsedValue; } /** * @brief Resolves scalar elements, multi-line inline dictionaries, and arrays * @return The parsed node. */ SNode ParseValueNode() { SkipWhitespaceAndComments(); char c = Peek(); SNode sValue; if (c == '"' || c == '\'') { sValue.eType = ENodeType::node_value; sValue.ssValueRaw = ParseStringNode(); } else if (c == '{') { sValue.eType = ENodeType::node_table; Consume(); // Consume starting '{' while (!IsEof()) { SkipWhitespaceAndComments(); if (Peek() == '}') { Consume(); break; } ParseKeyValue(sValue); // Can append safely during the parsing lifecycle phase SkipWhitespaceAndComments(); if (Peek() == ',') { Consume(); } else if (Peek() == '}') { Consume(); break; } } // CRITICAL FIX: Lock the inline structure to prevent outside modifications // ONLY after it has completely finished parsing and its braces are closed! sValue.bIsInline = true; } else if (c == '[') { sValue.eType = ENodeType::node_array; sValue.bIsExplicitlyHeadered = false; // Literal array token Consume(); while (!IsEof()) { SkipWhitespaceAndComments(); if (Peek() == ']') { Consume(); break; } sValue.vecArray.push_back(ParseValueNode()); SkipWhitespaceAndComments(); if (Peek() == ',') { Consume(); } else if (Peek() == ']') { Consume(); break; } } } else { sValue.eType = ENodeType::node_value; std::string strRaw = ""; while (!IsEof() && Peek() != '\n' && Peek() != '\r' && Peek() != ',' && Peek() != '}' && Peek() != ']' && Peek() != '#') { char rc = Consume(); if (rc != ' ' && rc != '\t') strRaw += rc; } sValue.ssValueRaw = strRaw; } return sValue; } }; } // namespace sdv::toml::simple_parser #ifdef __GNUC__ #pragma GCC diagnostic pop #endif