/// Errors thrown by `TOMLReader` when parsing fails. public enum TOMLReaderError: Error, Equatable { /// The input contains invalid or unexpected syntax at a given line. case parseError(line: Int, message: String) } /// A minimal, hand-rolled TOML parser suitable for reading `config.toml` files. /// /// Supports string, integer, boolean values; arrays; nested tables via /// `[section]` headers; dotted keys (`a.b.c = val`); inline tables /// (`key = { a = 1, b = 2 }`); and `#`-style line comments. Designed solely /// for the GIR generator's configuration surface and does not implement the /// full TOML spec (no datetime, multiline literals, or array-of-tables). public enum TOMLReader { /// Parses a TOML-formatted string into a dictionary. /// /// - Parameter raw: The raw TOML content. /// - Returns: A `[String: Any]` where values are `String`, `Int`, `Bool`, /// `[String]`, `[String: Any]`, or nested dictionaries. /// - Throws: `TOMLReaderError` if parsing fails. public static func parse(_ raw: String) throws -> [String: Any] { var result: [String: Any] = [:] var currentSectionPath: [String] = [] let lines = raw.components(separatedBy: "\n") for (idx, line) in lines.enumerated() { let trimmed = stripComment(line).trimmingCharacters(in: .whitespaces) guard !trimmed.isEmpty else { continue } let lineNumber = idx + 1 if trimmed.hasPrefix("[") && !trimmed.hasPrefix("[[") && trimmed.hasSuffix("]") { // Section header: [section.sub] let inner = String(trimmed.dropFirst().dropLast()).trimmingCharacters(in: .whitespaces) currentSectionPath = splitDottedKey(inner).map { unquote($0) } continue } if let eqIdx = trimmed.firstIndex(of: "=") { let key = String(trimmed[.. String { var inString = false for (i, ch) in line.enumerated() { if ch == "\"" { inString.toggle() } if ch == "#" && !inString { let prevIdx = line.index(line.startIndex, offsetBy: i > 0 ? i - 1 : 0) if i == 0 || line[prevIdx] == " " || line[prevIdx] == "\t" { return String(line[.. String { let t = s.trimmingCharacters(in: .whitespaces) guard t.hasPrefix("\"") && t.hasSuffix("\"") && t.count >= 2 else { return t } return String(t.dropFirst().dropLast()) } private static func splitDottedKey(_ key: String) -> [String] { var parts: [String] = [] var current = "" var inQuotes = false for ch in key { if ch == "\"" { inQuotes.toggle(); continue } if ch == "." && !inQuotes { if !current.isEmpty { parts.append(current); current = "" } continue } current.append(ch) } if !current.isEmpty { parts.append(current) } return parts } fileprivate static func parseValue(_ s: inout String, lineNumber: Int) throws -> Any { s = s.trimmingCharacters(in: .whitespaces) if s.hasPrefix("[") { let closing = findClosing(s, open: "[", close: "]") let inner = String(s[s.index(s.startIndex, offsetBy: 1).. String.Index { var depth = 0 var inStr = false for i in s.indices { if s[i] == "\"" { inStr.toggle(); continue } if inStr { continue } if s[i] == open { depth += 1 } else if s[i] == close { depth -= 1; if depth == 0 { return i } } } return s.endIndex } private static func convertInlineTable(_ inner: String) -> String { var result = "" var current = "" var depth = 0 var inStr = false for ch in inner { if ch == "\"" { inStr.toggle(); current.append(ch); continue } if inStr { current.append(ch); continue } if ch == "{" || ch == "[" { depth += 1; current.append(ch) } else if ch == "}" || ch == "]" { depth -= 1; current.append(ch) } else if ch == "," && depth == 0 { let part = current.trimmingCharacters(in: .whitespaces) if !part.isEmpty { result += part + "\n" } current = "" } else { current.append(ch) } } let part = current.trimmingCharacters(in: .whitespaces) if !part.isEmpty { result += part + "\n" } return result } private static func setNested(_ root: inout [String: Any], path: [String], value: Any) { guard let first = path.first else { return } if path.count == 1 { root[first] = value } else { var inner = (root[first] as? [String: Any]) ?? [String: Any]() setNested(&inner, path: Array(path.dropFirst()), value: value) root[first] = inner } } } private struct ArrayParser { let text: String init(_ text: String) { self.text = text } func parse(lineNumber: Int) throws -> [Any] { var result: [Any] = [] var current = "" var depth = 0 var inStr = false for ch in text { if ch == "\"" { inStr.toggle(); current.append(ch); continue } if inStr { current.append(ch); continue } if ch == "[" || ch == "{" { depth += 1; current.append(ch) } else if ch == "]" || ch == "}" { depth -= 1; current.append(ch) } else if ch == "," && depth == 0 { let val = current.trimmingCharacters(in: .whitespaces) if !val.isEmpty { var v = val result.append(try TOMLReader.parseValue(&v, lineNumber: lineNumber)) } current = "" } else { current.append(ch) } } let last = current.trimmingCharacters(in: .whitespaces) if !last.isEmpty { var v = last result.append(try TOMLReader.parseValue(&v, lineNumber: lineNumber)) } return result } }