From d56e777025f3bda29feaec22c125b0e7ab975e0c Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Sun, 14 Dec 2025 16:52:57 +0100 Subject: Format, make map equatable --- Sources/WmapParser/KeywordParser.swift | 352 +++++++++++++++++---------------- 1 file changed, 177 insertions(+), 175 deletions(-) (limited to 'Sources/WmapParser/KeywordParser.swift') diff --git a/Sources/WmapParser/KeywordParser.swift b/Sources/WmapParser/KeywordParser.swift index b560d6a..25f0ecd 100644 --- a/Sources/WmapParser/KeywordParser.swift +++ b/Sources/WmapParser/KeywordParser.swift @@ -1,186 +1,188 @@ extension WmapParser { - /// Checks which keyword we're dealing with and routes it to the right parser. - static func parseKeywordLine( - _ bytes: T, _ start: Int, _ end: Int, - _ context: inout KeywordParserContext - ) where T.Element == UInt8, T.Index == Int { - guard let (keyword, rest) = extractKeyword(bytes, start, end) else { return } - - if matchesKeyword(keyword, kInertiaKeyword), let inertia = parseInertia(bytes, rest, end) { - context.inertias.append(inertia) - } else if matchesKeyword(keyword, kEvolutionKeyword), - let evolution = parseEvolution(bytes, rest, end) { - context.evolutions.append(evolution) - } else if matchesKeyword(keyword, kNoteKeyword), let note = parseNote(bytes, rest, end) { - context.notes.append(note) - } else if matchesKeyword(keyword, kGroupKeyword), let group = parseGroup(bytes, rest, end) { - context.groups.append(group) - } else if let stageNumber = isStageKeyword(keyword), - let stage = parseStage(bytes, stageNumber, rest, end) { - context.stages.append(stage) - } + /// Checks which keyword we're dealing with and routes it to the right parser. + static func parseKeywordLine( + _ bytes: T, _ start: Int, _ end: Int, + _ context: inout KeywordParserContext + ) where T.Element == UInt8, T.Index == Int { + guard let (keyword, rest) = extractKeyword(bytes, start, end) else { return } + + if matchesKeyword(keyword, kInertiaKeyword), let inertia = parseInertia(bytes, rest, end) { + context.inertias.append(inertia) + } else if matchesKeyword(keyword, kEvolutionKeyword), + let evolution = parseEvolution(bytes, rest, end) { + context.evolutions.append(evolution) + } else if matchesKeyword(keyword, kNoteKeyword), let note = parseNote(bytes, rest, end) { + context.notes.append(note) + } else if matchesKeyword(keyword, kGroupKeyword), let group = parseGroup(bytes, rest, end) { + context.groups.append(group) + } else if let stageNumber = isStageKeyword(keyword), + let stage = parseStage(bytes, stageNumber, rest, end) { + context.stages.append(stage) } - - /// Extracts a keyword from inside brackets. + the index of the rest of the - /// line. - @inline(__always) - static func extractKeyword(_ bytes: T, _ start: Int, _ end: Int) -> (T.SubSequence, Int)? - where T.Element == UInt8, T.Index == Int { - var index = start + 1 // Skip '[' - let keywordStart = index - - while index < end && bytes[index] != kCloseSquareBracket { index += 1 } // Find ']' - if index >= end { return nil } - - let keywordEnd = index - index += 1 - - // Skip whitespace after ']' - index = skipWhitespace(bytes, end, index) - - return (bytes[keywordStart..(_ keyword: T) -> StageNumber? - where T.Element == UInt8, T.Index == Int { - if matchesKeyword(keyword, kIKeyword) { - return .stageI - } - if matchesKeyword(keyword, kIIKeyword) { - return .stageII - } - if matchesKeyword(keyword, kIIIKeyword) { - return .stageIII - } - if matchesKeyword(keyword, kIVKeyword) { - return .stageIV - } - return nil - } - - /// Compares a collection of bytes against a list of bytes. - /// Short-Circuits by length, only two have the same length (ii and iv) - @inline(__always) - static func matchesKeyword(_ keyword: T, _ expected: [UInt8]) -> Bool - where T.Element == UInt8, T.Index == Int { - guard keyword.count == expected.count else { return false } - var keywordIndex = keyword.startIndex - for index in 0..= kCapitalA && currentKeyword <= kCapitalZ) - ? currentKeyword + kCapsToLowercaseDistance : currentKeyword - if lowercasedKeyword != expectedKeyword { return false } - keywordIndex += 1 - } - return true - } - - /// Parses the stage. Assumes we got the stage number in the keyword check - /// to avoid checking twice. - static func parseStage( - _ bytes: T, _ stageNumber: StageNumber, _ start: Int, _ end: Int - ) -> Stage? - where T.Element == UInt8, T.Index == Int { - guard let value = parseDouble(bytes, start, end) else { return nil } - - return Stage(stage: stageNumber, value: value) - } - - /// Parses the note coordinates and text. - static func parseNote(_ bytes: T, _ start: Int, _ end: Int) -> Note? - where T.Element == UInt8, T.Index == Int { - var index = skipWhitespace(bytes, end, start) - - guard index < end && bytes[index] == kOpenParenthesis else { return nil } - index += 1 - - guard let (coordinates, endIndex) = parseCoordinates(bytes, index, end) else { - return nil - } - index = skipWhitespace(bytes, end, endIndex) - - let textEnd = shaveWhitespace(bytes, index, end) - - // Why not String(data:encoding:)? This is much faster. - // swiftlint:disable:next optional_data_string_conversion - let text = String(decoding: bytes[index..(_ bytes: T, _ start: Int, _ end: Int) -> Group? - where T.Element == UInt8, T.Index == Int { - var components = [String]() - var index = start - var componentStart = start - - while index <= end { - if index == end || bytes[index] == kComma { - let trimStart = skipWhitespace(bytes, index, componentStart) - let trimEnd = shaveWhitespace(bytes, trimStart, index) - - if trimStart < trimEnd { - // Why not String(data:encoding:)? This is much faster. - // swiftlint:disable:next optional_data_string_conversion - let component = String(decoding: bytes[trimStart..(_ bytes: T, _ start: Int, _ end: Int) -> Inertia? - where T.Element == UInt8, T.Index == Int { - let trimStart = skipWhitespace(bytes, end, start) - let trimEnd = shaveWhitespace(bytes, trimStart, end) - - guard trimStart < trimEnd else { return nil } - - // Why not String(data:encoding:)? This is much faster. - // swiftlint:disable:next optional_data_string_conversion - let component = String(decoding: bytes[trimStart..(_ bytes: T, _ start: Int, _ end: Int) -> ( + T.SubSequence, Int + )? + where T.Element == UInt8, T.Index == Int { + var index = start + 1 // Skip '[' + let keywordStart = index + + while index < end && bytes[index] != kCloseSquareBracket { index += 1 } // Find ']' + if index >= end { return nil } + + let keywordEnd = index + index += 1 + + // Skip whitespace after ']' + index = skipWhitespace(bytes, end, index) + + return (bytes[keywordStart..(_ keyword: T) -> StageNumber? + where T.Element == UInt8, T.Index == Int { + if matchesKeyword(keyword, kIKeyword) { + return .stageI + } + if matchesKeyword(keyword, kIIKeyword) { + return .stageII + } + if matchesKeyword(keyword, kIIIKeyword) { + return .stageIII + } + if matchesKeyword(keyword, kIVKeyword) { + return .stageIV + } + return nil + } + + /// Compares a collection of bytes against a list of bytes. + /// Short-Circuits by length, only two have the same length (ii and iv) + @inline(__always) + static func matchesKeyword(_ keyword: T, _ expected: [UInt8]) -> Bool + where T.Element == UInt8, T.Index == Int { + guard keyword.count == expected.count else { return false } + var keywordIndex = keyword.startIndex + for index in 0..= kCapitalA && currentKeyword <= kCapitalZ) + ? currentKeyword + kCapsToLowercaseDistance : currentKeyword + if lowercasedKeyword != expectedKeyword { return false } + keywordIndex += 1 + } + return true + } + + /// Parses the stage. Assumes we got the stage number in the keyword check + /// to avoid checking twice. + static func parseStage( + _ bytes: T, _ stageNumber: StageNumber, _ start: Int, _ end: Int + ) -> Stage? + where T.Element == UInt8, T.Index == Int { + guard let value = parseDouble(bytes, start, end) else { return nil } + + return Stage(stage: stageNumber, value: value) + } + + /// Parses the note coordinates and text. + static func parseNote(_ bytes: T, _ start: Int, _ end: Int) -> Note? + where T.Element == UInt8, T.Index == Int { + var index = skipWhitespace(bytes, end, start) + + guard index < end && bytes[index] == kOpenParenthesis else { return nil } + index += 1 + + guard let (coordinates, endIndex) = parseCoordinates(bytes, index, end) else { + return nil + } + index = skipWhitespace(bytes, end, endIndex) + + let textEnd = shaveWhitespace(bytes, index, end) + + // Why not String(data:encoding:)? This is much faster. + // swiftlint:disable:next optional_data_string_conversion + let text = String(decoding: bytes[index..(_ bytes: T, _ start: Int, _ end: Int) -> Group? + where T.Element == UInt8, T.Index == Int { + var components = [String]() + var index = start + var componentStart = start + + while index <= end { + if index == end || bytes[index] == kComma { + let trimStart = skipWhitespace(bytes, index, componentStart) + let trimEnd = shaveWhitespace(bytes, trimStart, index) + + if trimStart < trimEnd { + // Why not String(data:encoding:)? This is much faster. + // swiftlint:disable:next optional_data_string_conversion + let component = String(decoding: bytes[trimStart..(_ bytes: T, _ start: Int, _ end: Int) -> Evolution? - where T.Element == UInt8, T.Index == Int { - var signIndex: Int? - var sign = 1.0 + componentStart = index + 1 + } + index += 1 + } - var index = start - while index < end { - if bytes[index] == kPlus || bytes[index] == kDash { - signIndex = index - sign = bytes[index] == 45 ? -1.0 : 1.0 - break - } - index += 1 - } + return components.isEmpty ? nil : Group(components: components) + } + + /// Parses the component label for inertia + static func parseInertia(_ bytes: T, _ start: Int, _ end: Int) -> Inertia? + where T.Element == UInt8, T.Index == Int { + let trimStart = skipWhitespace(bytes, end, start) + let trimEnd = shaveWhitespace(bytes, trimStart, end) + + guard trimStart < trimEnd else { return nil } + + // Why not String(data:encoding:)? This is much faster. + // swiftlint:disable:next optional_data_string_conversion + let component = String(decoding: bytes[trimStart..(_ bytes: T, _ start: Int, _ end: Int) -> Evolution? + where T.Element == UInt8, T.Index == Int { + var signIndex: Int? + var sign = 1.0 + + var index = start + while index < end { + if bytes[index] == kPlus || bytes[index] == kDash { + signIndex = index + sign = bytes[index] == 45 ? -1.0 : 1.0 + break + } + index += 1 + } - guard let signIndex, signIndex > start else { return nil } + guard let signIndex, signIndex > start else { return nil } - let trimStart = skipWhitespace(bytes, signIndex, start) - let trimEnd = shaveWhitespace(bytes, trimStart, signIndex) + let trimStart = skipWhitespace(bytes, signIndex, start) + let trimEnd = shaveWhitespace(bytes, trimStart, signIndex) - guard trimStart < trimEnd else { return nil } + guard trimStart < trimEnd else { return nil } - // Why not String(data:encoding:)? This is much faster. - // swiftlint:disable:next optional_data_string_conversion - let component = String(decoding: bytes[trimStart..