import Foundation public struct WmapParser { // Public API ////////////////////////////////////////////////////////////////// /// Parses a wmap source string. public static func parse(_ source: String) -> Map { source.utf8.withContiguousStorageIfAvailable { bytes in parseBytes(bytes) } ?? parseBytes(Array(source.utf8)) } // Parsers ///////////////////////////////////////////////////////////////////// /// Main parsing function, we work on bytes as it's faster. private static 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 static 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) } } }