From 03f1a7389afeadf9aa2b1d62a38e5cb0a2a7e60d Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Sat, 13 Dec 2025 01:17:52 +0100 Subject: Initial implementation --- Sources/WmapParser/WmapParser.swift | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Sources/WmapParser/WmapParser.swift (limited to 'Sources/WmapParser/WmapParser.swift') diff --git a/Sources/WmapParser/WmapParser.swift b/Sources/WmapParser/WmapParser.swift new file mode 100644 index 0000000..f2bef8f --- /dev/null +++ b/Sources/WmapParser/WmapParser.swift @@ -0,0 +1,79 @@ +import Foundation + +// Public API ////////////////////////////////////////////////////////////////// + +/// Parses a wmap source string. +public func parse(_ source: String) -> Map { + source.utf8.withContiguousStorageIfAvailable { bytes in + parseBytes(bytes) + } ?? parseBytes(Array(source.utf8)) +} + +// Parsers ///////////////////////////////////////////////////////////////////// + +struct LineParserContext { + var components: [Component] + var dependencies: [Dependency] + var keywordContext: KeywordParserContext +} + +/// Main parsing function, we work on bytes as it's faster. +private func parseBytes(_ bytes: T) -> Map where T.Element == UInt8, T.Index == Int { + let estimatedLines = bytes.count / 30 + + var context = LineParserContext( + components: [], + dependencies: [], + keywordContext: KeywordParserContext( + stages: [], + notes: [], + groups: [], + inertias: [], + evolutions: [] + ) + ) + + context.components.reserveCapacity(estimatedLines) + context.dependencies.reserveCapacity(estimatedLines) + context.keywordContext.notes.reserveCapacity(estimatedLines / 10) + context.keywordContext.stages.reserveCapacity(4) + context.keywordContext.groups.reserveCapacity(estimatedLines / 20) + context.keywordContext.inertias.reserveCapacity(estimatedLines / 20) + context.keywordContext.evolutions.reserveCapacity(estimatedLines / 20) + + var index = 0 + let length = bytes.count + + while index < length { + guard let (start, end) = extractLine(bytes, &index, length) else { continue } + parseLine(bytes, start, end, &context) + } + + return Map( + components: context.components, + dependencies: context.dependencies, + notes: context.keywordContext.notes, + stages: context.keywordContext.stages, + groups: context.keywordContext.groups, + inertias: context.keywordContext.inertias, + evolutions: context.keywordContext.evolutions + ) +} + +/// Given a trimmed line, checks if it's a keyword, dependency or component +/// and parses it. +@inline(__always) +private func parseLine( + _ bytes: T, _ start: Int, _ end: Int, + _ context: inout LineParserContext +) where T.Element == UInt8, T.Index == Int { + if bytes[start] == kOpenSquareBracket { + parseKeywordLine(bytes, start, end, &context.keywordContext) + } else if let separatorIndex = hasDependency(bytes, start, end) { + if let dependency = parseDependency(bytes, start, end, separatorIndex) { + context.dependencies.append(dependency) + } + } else if let component = parseComponent(bytes, start, end) { + context.components.append(component) + } +} -- cgit