Program Listing for File simple_toml.h#
↰ Return to documentation for file (support\simple_toml.h)
#pragma once
#include <algorithm>
#include <cctype>
#include <functional>
#include <map>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include <charconv>
#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
namespace sdv::toml::simple_parser
{
enum class ENodeType
{
node_unknown,
node_table,
node_array,
node_value
};
// Forward declaration of the main data structure
struct SNode;
using TTableMap = std::map<std::string, SNode>;
using TNodeArray = std::vector<SNode>;
struct SNode
{
ENodeType eType = ENodeType::node_unknown;
std::string ssKey;
std::string ssValueRaw = "";
TTableMap mapTable = {};
TNodeArray vecArray = {};
bool bIsInline = false;
bool bIsExplicitlyHeadered = false;
operator bool() const noexcept
{
return eType != ENodeType::node_unknown;
}
bool IsValid() const noexcept
{
return eType != ENodeType::node_unknown;
}
bool IsTable() const noexcept
{
return eType == ENodeType::node_table;
}
bool IsArray() const noexcept
{
return eType == ENodeType::node_array;
}
bool IsValue() const noexcept
{
return eType == ENodeType::node_value;
}
ENodeType GetType() const noexcept
{
return eType;
}
const std::string& GetName() const noexcept
{
return ssKey;
}
const std::string& GetValue() const noexcept
{
return ssValueRaw;
}
template <typename TType>
TType GetValue() const
{
if constexpr (std::is_same_v<TType, bool>)
{
return ssValueRaw == "true" || GetValue<int>() != 0;
}
else if constexpr (std::is_integral_v<TType> || std::is_floating_point_v<TType>)
{
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<TType, std::string>)
{
return ssValueRaw;
}
else
return {};
}
const TTableMap& GetTable() const noexcept
{
return mapTable;
}
const TNodeArray& GetArray() const noexcept
{
return vecArray;
}
const SNode GetDirect(std::string_view svPath) const noexcept
{
static const SNode sNodeEmpty;
std::reference_wrapper<const SNode> 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<size_t>(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;
}
};
class CParser
{
public:
// cppcheck-suppress passedByValue
CParser(std::string_view svInput) : m_svSrc(svInput), m_nPos(0)
{
Parse();
}
const SNode& Root() const noexcept
{
return m_sRoot;
}
private:
std::string_view m_svSrc;
size_t m_nPos;
SNode m_sRoot;
std::vector<std::string> m_vecExplicitlyDefinedTables;
SNode Parse()
{
m_sRoot.eType = ENodeType::node_table;
std::vector<std::string> 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;
}
SNode& NavigateToTable(const std::vector<std::string>& rvecPath)
{
std::reference_wrapper<SNode> 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();
}
bool IsEof() const noexcept
{
return m_nPos >= m_svSrc.size();
}
char Peek() const noexcept
{
return IsEof() ? '\0' : m_svSrc[m_nPos];
}
char Consume() noexcept
{
return IsEof() ? '\0' : m_svSrc[m_nPos++];
}
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;
}
}
}
void SkipInlineWhitespace() noexcept
{
while (!IsEof() && (Peek() == ' ' || Peek() == '\t'))
Consume();
}
std::vector<std::string> ParseDottedKey()
{
std::vector<std::string> 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<unsigned char>(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;
}
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<char>(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.");
}
//std::vector<std::string> ParseTableHeader()
//{
// Consume(); // Consume standard structural '['
// bool bIsTableArray = false;
// if (Peek() == '[')
// {
// Consume();
// bIsTableArray = true;
// }
// std::vector<std::string> 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<SNode> 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<std::string> ParseTableHeader()
{
Consume(); // Consume standard structural '['
bool bIsTableArray = false;
if (Peek() == '[')
{
Consume();
bIsTableArray = true;
}
std::vector<std::string> 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<SNode> 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;
}
void ParseKeyValue(SNode& rsParentTable)
{
std::vector<std::string> vecKeys = ParseDottedKey();
SkipInlineWhitespace();
if (Consume() != '=')
throw std::runtime_error("Missing valid variable declaration assignment mapping.");
SkipInlineWhitespace();
std::reference_wrapper<SNode> 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;
}
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