diff options
Diffstat (limited to 'Sources/WmapParser/Utils.swift')
| -rw-r--r-- | Sources/WmapParser/Utils.swift | 136 |
1 files changed, 136 insertions, 0 deletions
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<T: Collection>(_ 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<T: Collection>(_ 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<T: Collection>(_ 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<T: Collection>(_ 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<T: Collection>(_ 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..<index], as: UTF8.self) + return Double(str) +} + +/// Advances an index by looking for whitespace left-to-right +func skipWhitespace<T: Collection>(_ 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<T: Collection>(_ 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<T: Collection>(_ 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<T: Collection>(_ 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..<trimEnd], as: UTF8.self) +} |