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/Utils.swift | 136 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 Sources/WmapParser/Utils.swift (limited to 'Sources/WmapParser/Utils.swift') diff --git a/Sources/WmapParser/Utils.swift b/Sources/WmapParser/Utils.swift new file mode 100644 index 0000000..8bea13a --- /dev/null +++ b/Sources/WmapParser/Utils.swift @@ -0,0 +1,136 @@ +/// Check if a byte is a digit +@inline(__always) +func isDigit(_ byte: UInt8) -> Bool { + byte >= kZero && byte <= kNine +} + +/// Looks for x and y separated by a comma. +func parseCoordinates(_ bytes: T, _ start: Int, _ end: Int) -> ( + MapPoint, Int +)? +where T.Element == UInt8, T.Index == Int { + var index = skipWhitespace(bytes, end, start) + + guard let (xCoordinate, nextIndex) = parseSingleCoordinate(bytes, index, end) else { + return nil + } + index = nextIndex + + guard expectComma(bytes, &index, end) else { return nil } + + guard let (yCoordinate, nextIndex) = parseSingleCoordinate(bytes, index, end) else { + return nil + } + index = nextIndex + + guard expectClosingParen(bytes, &index, end) else { return nil } + + return (MapPoint(xCoordinate: xCoordinate, yCoordinate: yCoordinate), index) +} + +/// Checks if a candidate for a coordinate is a digit, and parses it. +@inline(__always) +func parseSingleCoordinate(_ bytes: T, _ start: Int, _ end: Int) -> (Double, Int)? +where T.Element == UInt8, T.Index == Int { + var index = start + while index < end && (isDigit(bytes[index]) || bytes[index] == 46) { index += 1 } + guard index > start else { return nil } + guard let value = parseDouble(bytes, start, index) else { return nil } + return (value, index) +} + +/// Advances the index until we find a comma. +@inline(__always) +func expectComma(_ bytes: T, _ index: inout Int, _ end: Int) -> Bool +where T.Element == UInt8, T.Index == Int { + index = skipWhitespace(bytes, end, index) + guard index < end && bytes[index] == kComma else { return false } + index += 1 + index = skipWhitespace(bytes, end, index) + return true +} + +/// Advances the index until we find a closing parenthesis +@inline(__always) +func expectClosingParen(_ bytes: T, _ index: inout Int, _ end: Int) -> Bool +where T.Element == UInt8, T.Index == Int { + index = skipWhitespace(bytes, end, index) + guard index < end && bytes[index] == kCloseParenthesis else { return false } + index += 1 + return true +} + +/// Parses a double +@inline(__always) +func parseDouble(_ bytes: T, _ start: Int, _ end: Int) -> Double? +where T.Element == UInt8, T.Index == Int { + var index = skipWhitespace(bytes, end, start) + + let numStart = index + while index < end && (isDigit(bytes[index]) || bytes[index] == kPeriod) { index += 1 } + + guard index > numStart else { return nil } + + // Why not String(data:encoding:)? This is much faster. + // swiftlint:disable:next optional_data_string_conversion + let str = String(decoding: bytes[numStart..(_ bytes: T, _ end: Int, _ start: Int) -> Int +where T.Element == UInt8, T.Index == Int { + var index = start + while index < end && (bytes[index] == kSpace || bytes[index] == kTab) { index += 1 } + return index +} + +/// Decreases an index by looking for whitespace right-to-left +func shaveWhitespace(_ bytes: T, _ start: Int, _ end: Int) -> Int +where T.Element == UInt8, T.Index == Int { + var index = end + while index > start && (bytes[index - 1] == kSpace || bytes[index - 1] == kTab) { index -= 1 } + return index +} + +/// Given the bytes buffer, finds the next non-empty line. +@inline(__always) +func extractLine(_ bytes: T, _ index: inout Int, _ length: Int) -> ( + Int, Int +)? +where T.Element == UInt8, T.Index == Int { + while index < length && (bytes[index] == kNewline || bytes[index] == kCarriageReturn) { + index += 1 + } + let lineStart = index + + while index < length && bytes[index] != kNewline && bytes[index] != kCarriageReturn { index += 1 } + if lineStart == index { return nil } + + var start = lineStart + var end = index + + start = skipWhitespace(bytes, end, start) + end = shaveWhitespace(bytes, start, end) + + if start == end { return nil } + + return (start, end) +} + +/// Trims a byte collection and returns a string +@inline(__always) +func extractTrimmedString(_ bytes: T, _ start: Int, _ end: Int) -> String? +where T.Element == UInt8, T.Index == Int { + var trimStart = start + var trimEnd = end + + trimStart = skipWhitespace(bytes, trimEnd, trimStart) + trimEnd = shaveWhitespace(bytes, trimStart, trimEnd) + + guard trimStart < trimEnd else { return nil } + + // Why not String(data:encoding:)? This is much faster. + // swiftlint:disable:next optional_data_string_conversion + return String(decoding: bytes[trimStart..