diff options
Diffstat (limited to 'Sources/WmapParser/WmapParser.swift')
| -rw-r--r-- | Sources/WmapParser/WmapParser.swift | 79 |
1 files changed, 79 insertions, 0 deletions
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<T: Collection>(_ 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<T: Collection>( + _ 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) + } +} |