diff --git a/Sources/BinaryParsing/Macros/MagicNumber.swift b/Sources/BinaryParsing/Macros/MagicNumber.swift index 6088c26..2058ee9 100644 --- a/Sources/BinaryParsing/Macros/MagicNumber.swift +++ b/Sources/BinaryParsing/Macros/MagicNumber.swift @@ -15,7 +15,7 @@ func _loadAndCheckDirectBytes< >( parsing input: inout ParserSpan, bigEndianValue: T -) throws { +) throws(ParsingError) { let loadedValue = try T(parsingBigEndian: &input) guard loadedValue == bigEndianValue else { throw ParsingError( @@ -29,7 +29,7 @@ func _loadAndCheckDirectBytesByteOrder< >( parsing input: inout ParserSpan, bigEndianValue: T -) throws -> Endianness { +) throws(ParsingError) -> Endianness { let loadedValue = try T(parsingBigEndian: &input) if loadedValue == bigEndianValue { return .big diff --git a/Sources/BinaryParsing/Operations/Optionators.swift b/Sources/BinaryParsing/Operations/Optionators.swift index eaa5408..1875b34 100644 --- a/Sources/BinaryParsing/Operations/Optionators.swift +++ b/Sources/BinaryParsing/Operations/Optionators.swift @@ -108,7 +108,7 @@ extension Optional where Wrapped: Comparable { guard lhs <= rhs else { return nil } return lhs.. ClosedRange? { guard let lhs, let rhs else { return nil } diff --git a/Sources/BinaryParsing/Operations/ThrowingOperations.swift b/Sources/BinaryParsing/Operations/ThrowingOperations.swift index 1cda5c9..1c8b169 100644 --- a/Sources/BinaryParsing/Operations/ThrowingOperations.swift +++ b/Sources/BinaryParsing/Operations/ThrowingOperations.swift @@ -12,7 +12,7 @@ extension Collection { @inlinable public subscript(throwing i: Index) -> Element { - get throws { + get throws(ParsingError) { guard (startIndex.. Self { + public func addingThrowingOnOverflow(_ other: Self) throws(ParsingError) + -> Self + { let (result, overflow) = addingReportingOverflow(other) if overflow { throw ParsingError(statusOnly: .invalidValue) @@ -57,7 +60,9 @@ extension FixedWidthInteger { } @inlinable - public func subtractingThrowingOnOverflow(_ other: Self) throws -> Self { + public func subtractingThrowingOnOverflow(_ other: Self) throws(ParsingError) + -> Self + { let (result, overflow) = subtractingReportingOverflow(other) if overflow { throw ParsingError(statusOnly: .invalidValue) @@ -66,7 +71,9 @@ extension FixedWidthInteger { } @inlinable - public func multipliedThrowingOnOverflow(by other: Self) throws -> Self { + public func multipliedThrowingOnOverflow(by other: Self) throws(ParsingError) + -> Self + { let (result, overflow) = multipliedReportingOverflow(by: other) if overflow { throw ParsingError(statusOnly: .invalidValue) @@ -75,7 +82,9 @@ extension FixedWidthInteger { } @inlinable - public func dividedThrowingOnOverflow(by other: Self) throws -> Self { + public func dividedThrowingOnOverflow(by other: Self) throws(ParsingError) + -> Self + { let (result, overflow) = dividedReportingOverflow(by: other) if overflow { throw ParsingError(statusOnly: .invalidValue) @@ -84,7 +93,8 @@ extension FixedWidthInteger { } @inlinable - public func remainderThrowingOnOverflow(dividingBy other: Self) throws -> Self + public func remainderThrowingOnOverflow(dividingBy other: Self) + throws(ParsingError) -> Self { let (result, overflow) = remainderReportingOverflow(dividingBy: other) if overflow { @@ -96,28 +106,35 @@ extension FixedWidthInteger { // MARK: Mutating arithmetic @inlinable - public mutating func addThrowingOnOverflow(_ other: Self) throws { + public mutating func addThrowingOnOverflow(_ other: Self) throws(ParsingError) + { self = try self.addingThrowingOnOverflow(other) } @inlinable - public mutating func subtractThrowingOnOverflow(_ other: Self) throws { + public mutating func subtractThrowingOnOverflow(_ other: Self) + throws(ParsingError) + { self = try self.subtractingThrowingOnOverflow(other) } @inlinable - public mutating func multiplyThrowingOnOverflow(by other: Self) throws { + public mutating func multiplyThrowingOnOverflow(by other: Self) + throws(ParsingError) + { self = try self.multipliedThrowingOnOverflow(by: other) } @inlinable - public mutating func divideThrowingOnOverflow(by other: Self) throws { + public mutating func divideThrowingOnOverflow(by other: Self) + throws(ParsingError) + { self = try self.dividedThrowingOnOverflow(by: other) } @inlinable public mutating func formRemainderThrowingOnOverflow(dividingBy other: Self) - throws + throws(ParsingError) { self = try self.remainderThrowingOnOverflow(dividingBy: other) } diff --git a/Sources/BinaryParsing/Parser Types/ParserRange.swift b/Sources/BinaryParsing/Parser Types/ParserRange.swift index 4593de1..a9e4571 100644 --- a/Sources/BinaryParsing/Parser Types/ParserRange.swift +++ b/Sources/BinaryParsing/Parser Types/ParserRange.swift @@ -35,7 +35,8 @@ public struct ParserRange: Hashable { } extension ParserRange { - public func slicing>(_ coll: C) throws -> C.SubSequence + public func slicing>(_ coll: C) throws(ParsingError) + -> C.SubSequence where C.Index == Int { let validRange = coll.startIndex...coll.endIndex guard validRange.contains(range.lowerBound), @@ -49,7 +50,7 @@ extension ParserRange { extension RandomAccessCollection where Index == Int { public subscript(_ range: ParserRange) -> SubSequence { - get throws { + get throws(ParsingError) { let validRange = startIndex...endIndex guard validRange.contains(range.lowerBound), validRange.contains(range.upperBound) diff --git a/Sources/BinaryParsing/Parser Types/ParserSource.swift b/Sources/BinaryParsing/Parser Types/ParserSource.swift index 92a8ba3..6f55dec 100644 --- a/Sources/BinaryParsing/Parser Types/ParserSource.swift +++ b/Sources/BinaryParsing/Parser Types/ParserSource.swift @@ -15,20 +15,20 @@ public import Foundation public protocol ExpressibleByParsing { @lifetime(&input) - init(parsing input: inout ParserSpan) throws + init(parsing input: inout ParserSpan) throws(ThrownParsingError) } extension ExpressibleByParsing { - public init(parsing data: some RandomAccessCollection) throws { + public init(parsing data: some RandomAccessCollection) + throws(ThrownParsingError) + { guard - let result = try data.withParserSpanIfAvailable({ span in + let result = try data.withParserSpanIfAvailable({ + (span) throws(ThrownParsingError) in try Self.init(parsing: &span) }) else { - throw ParsingError( - status: .invalidValue, - location: 0, - message: "Provided data type does not support contiguous access.") + throw ParsingError(statusOnly: .invalidValue) } self = result } @@ -37,20 +37,32 @@ extension ExpressibleByParsing { extension RandomAccessCollection { @inlinable public func withParserSpanIfAvailable( - _ body: (inout ParserSpan) throws -> T - ) throws -> T? { + _ body: (inout ParserSpan) throws(ThrownParsingError) -> T + ) throws(ThrownParsingError) -> T? { #if canImport(Foundation) if let data = self as? Foundation.Data { - return try data.withUnsafeBytes { buffer -> T in - var span = ParserSpan(_unsafeBytes: buffer) - return try body(&span) + do { + return try data.withUnsafeBytes { buffer -> T in + var span = ParserSpan(_unsafeBytes: buffer) + return try body(&span) + } + } catch { + // Workaround for lack of typed-throwing API on Data + // swift-format-ignore: NeverForceUnwrap + throw error as! ThrownParsingError } } #endif - return try self.withContiguousStorageIfAvailable { buffer in - let rawBuffer = UnsafeRawBufferPointer(buffer) - var span = ParserSpan(_unsafeBytes: rawBuffer) - return try body(&span) + do { + return try self.withContiguousStorageIfAvailable { buffer in + let rawBuffer = UnsafeRawBufferPointer(buffer) + var span = ParserSpan(_unsafeBytes: rawBuffer) + return try body(&span) + } + } catch { + // Workaround for lack of typed-throwing API on Collection + // swift-format-ignore: NeverForceUnwrap + throw error as! ThrownParsingError } } } @@ -58,20 +70,28 @@ extension RandomAccessCollection { // MARK: ParserSpanProvider public protocol ParserSpanProvider { - func withParserSpan(_ body: (inout ParserSpan) throws -> T) throws -> T + func withParserSpan( + _ body: (inout ParserSpan) throws(ThrownParsingError) -> T + ) throws(ThrownParsingError) -> T } #if canImport(Foundation) extension Data: ParserSpanProvider { @inlinable - public func withParserSpan(_ body: (inout ParserSpan) throws -> T) throws - -> T - { - try withUnsafeBytes { buffer -> T in - // FIXME: RawSpan getter - // var span = ParserSpan(buffer.bytes) - var span = ParserSpan(_unsafeBytes: buffer) - return try body(&span) + public func withParserSpan( + _ body: (inout ParserSpan) throws(ThrownParsingError) -> T + ) throws(ThrownParsingError) -> T { + do { + return try withUnsafeBytes { buffer -> T in + // FIXME: RawSpan getter + // var span = ParserSpan(buffer.bytes) + var span = ParserSpan(_unsafeBytes: buffer) + return try body(&span) + } + } catch { + // Workaround for lack of typed-throwing API on Data + // swift-format-ignore: NeverForceUnwrap + throw error as! ThrownParsingError } } @@ -79,17 +99,23 @@ extension Data: ParserSpanProvider { @inlinable public func withParserSpan( usingRange range: inout ParserRange, - _ body: (inout ParserSpan) throws -> T - ) rethrows -> T { - try withUnsafeBytes { buffer -> T in - // FIXME: RawSpan getter - // var span = try ParserSpan(buffer.bytes) - var span = try ParserSpan(_unsafeBytes: buffer) - .seeking(toRange: range) - defer { - range = span.parserRange + _ body: (inout ParserSpan) throws(ThrownParsingError) -> T + ) throws(ThrownParsingError) -> T { + do { + return try withUnsafeBytes { (buffer) throws(ThrownParsingError) -> T in + // FIXME: RawSpan getter + // var span = try ParserSpan(buffer.bytes) + var span = try ParserSpan(_unsafeBytes: buffer) + .seeking(toRange: range) + defer { + range = span.parserRange + } + return try body(&span) } - return try body(&span) + } catch { + // Workaround for lack of typed-throwing API on Data + // swift-format-ignore: NeverForceUnwrap + throw error as! ThrownParsingError } } } @@ -97,21 +123,27 @@ extension Data: ParserSpanProvider { extension ParserSpanProvider where Self: RandomAccessCollection { @inlinable - public func withParserSpan(_ body: (inout ParserSpan) throws -> T) throws - -> T - { - guard - let result = try self.withContiguousStorageIfAvailable({ buffer in - // FIXME: RawSpan getter - // var span = ParserSpan(UnsafeRawBufferPointer(buffer).bytes) - let rawBuffer = UnsafeRawBufferPointer(buffer) - var span = ParserSpan(_unsafeBytes: rawBuffer) - return try body(&span) - }) - else { - throw ParsingError(status: .userError, location: 0) + public func withParserSpan( + _ body: (inout ParserSpan) throws(ThrownParsingError) -> T + ) throws(ThrownParsingError) -> T { + do { + guard + let result = try self.withContiguousStorageIfAvailable({ buffer in + // FIXME: RawSpan getter + // var span = ParserSpan(UnsafeRawBufferPointer(buffer).bytes) + let rawBuffer = UnsafeRawBufferPointer(buffer) + var span = ParserSpan(_unsafeBytes: rawBuffer) + return try body(&span) + }) + else { + throw ParsingError(status: .userError, location: 0) + } + return result + } catch { + // Workaround for lack of typed-throwing API on Collection + // swift-format-ignore: NeverForceUnwrap + throw error as! ThrownParsingError } - return result } } diff --git a/Sources/BinaryParsing/Parser Types/ParserSpan.swift b/Sources/BinaryParsing/Parser Types/ParserSpan.swift index 3260293..b740c04 100644 --- a/Sources/BinaryParsing/Parser Types/ParserSpan.swift +++ b/Sources/BinaryParsing/Parser Types/ParserSpan.swift @@ -121,10 +121,10 @@ extension ParserSpan { @_alwaysEmitIntoClient @inlinable @unsafe - public func withUnsafeBytes( - _ body: (UnsafeRawBufferPointer) throws -> T - ) rethrows -> T { - try _bytes.withUnsafeBytes { fullBuffer in + public func withUnsafeBytes( + _ body: (UnsafeRawBufferPointer) throws(E) -> T + ) throws(E) -> T { + try _bytes.withUnsafeBytes { (fullBuffer) throws(E) in let buffer = UnsafeRawBufferPointer( rebasing: fullBuffer[_lowerBound..<_upperBound]) return try body(buffer) @@ -137,7 +137,7 @@ extension ParserSpan { @usableFromInline internal mutating func _divide( atByteOffset count: some FixedWidthInteger - ) throws -> ParserSpan { + ) throws(ParsingError) -> ParserSpan { guard let count = Int(exactly: count), count >= 0 else { throw ParsingError(status: .invalidValue, location: startPosition) } @@ -186,9 +186,9 @@ extension ParserSpan { /// `atomically` guarantees that the input span isn't modified in that case. @inlinable @lifetime(&self) - public mutating func atomically(_ body: (inout ParserSpan) throws -> T) - rethrows -> T - { + public mutating func atomically( + _ body: (inout ParserSpan) throws(E) -> T + ) throws(E) -> T { // Make a mutable copy to perform the work in `body`. var copy = self let result = try body(©) diff --git a/Sources/BinaryParsing/Parser Types/ParsingError.swift b/Sources/BinaryParsing/Parser Types/ParsingError.swift index 0da430c..99370ef 100644 --- a/Sources/BinaryParsing/Parser Types/ParsingError.swift +++ b/Sources/BinaryParsing/Parser Types/ParsingError.swift @@ -9,6 +9,7 @@ // //===----------------------------------------------------------------------===// +/// An error produced during parsing. public struct ParsingError: Error { /// The different kinds of parsing errors. public struct Status: Equatable, Sendable { @@ -24,7 +25,8 @@ public struct ParsingError: Error { public static var insufficientData: Self { .init(rawValue: .insufficientData) } - /// Parsing failed due to an invalid parameter passed to a parsing function. + /// Parsing failed due to an invalid parsed value (for example, due to + /// overflow) or an invalid parameter passed to a parsing function. public static var invalidValue: Self { .init(rawValue: .invalidValue) } @@ -51,27 +53,14 @@ public struct ParsingError: Error { /// The user-provided error associated with this parsing error. public var userError: (any Error)? - @available(*, deprecated) @usableFromInline init( status: Status, - location: Int, - message: String = "", + location: Int? = nil, userError: (any Error)? = nil ) { self.status = status - self._location = location - self.userError = userError - } - - @usableFromInline - init( - status: Status, - location: Int, - userError: (any Error)? = nil - ) { - self.status = status - self._location = location + self._location = location ?? -1 self.userError = userError } #endif @@ -92,8 +81,7 @@ public struct ParsingError: Error { #if !$Embedded extension ParsingError { public init(userError: any Error) { - self = .init( - status: .userError, location: -1, userError: userError) + self = .init(status: .userError, userError: userError) } } @@ -107,3 +95,22 @@ extension ParsingError: CustomStringConvertible { } } #endif + +#if !$Embedded +/// An error thrown by the library user. +/// +/// In a build for non-embedded Swift, `ThrownParsingError` aliases `any Error`, +/// so you can throw an error of any kind from closures passed to methods that +/// are designated as `throws(ThrownParsingError)`. When the method throws an +/// error, it will always be either your error or an instance of +/// `ParsingError`. +/// +/// In a build for embedded Swift, `ThrownParsingError` instead aliases the +/// specific `ParsingError` type. Because embedded Swift supports only +/// fully-typed throws, and not the existential `any Error`, this allows you +/// to still take use error-throwing APIs in an embedded context. +public typealias ThrownParsingError = any Error +#else +// Documentation is built using the non-embedded build. +public typealias ThrownParsingError = ParsingError +#endif diff --git a/Sources/BinaryParsing/Parser Types/Seeking.swift b/Sources/BinaryParsing/Parser Types/Seeking.swift index 25e3dd6..b98470c 100644 --- a/Sources/BinaryParsing/Parser Types/Seeking.swift +++ b/Sources/BinaryParsing/Parser Types/Seeking.swift @@ -12,7 +12,9 @@ extension ParserSpan { @inlinable @lifetime(copy self) - public func seeking(toRange range: ParserRange) throws -> ParserSpan { + public func seeking(toRange range: ParserRange) + throws(ParsingError) -> ParserSpan + { var result = self try result.seek(toRange: range) return result @@ -20,8 +22,8 @@ extension ParserSpan { @inlinable @lifetime(copy self) - public func seeking(toRelativeOffset offset: some FixedWidthInteger) throws - -> ParserSpan + public func seeking(toRelativeOffset offset: some FixedWidthInteger) + throws(ParsingError) -> ParserSpan { var result = self try result.seek(toRelativeOffset: offset) @@ -30,8 +32,8 @@ extension ParserSpan { @inlinable @lifetime(copy self) - public func seeking(toAbsoluteOffset offset: some FixedWidthInteger) throws - -> ParserSpan + public func seeking(toAbsoluteOffset offset: some FixedWidthInteger) + throws(ParsingError) -> ParserSpan { var result = self try result.seek(toAbsoluteOffset: offset) @@ -40,8 +42,8 @@ extension ParserSpan { @inlinable @lifetime(copy self) - public func seeking(toOffsetFromEnd offset: some FixedWidthInteger) throws - -> ParserSpan + public func seeking(toOffsetFromEnd offset: some FixedWidthInteger) + throws(ParsingError) -> ParserSpan { var result = self try result.seek(toOffsetFromEnd: offset) @@ -52,7 +54,7 @@ extension ParserSpan { extension ParserSpan { @inlinable @lifetime(&self) - public mutating func seek(toRange range: ParserRange) throws { + public mutating func seek(toRange range: ParserRange) throws(ParsingError) { guard (0..._bytes.byteCount).contains(range.lowerBound), (0..._bytes.byteCount).contains(range.upperBound) else { @@ -65,7 +67,7 @@ extension ParserSpan { @inlinable @lifetime(&self) public mutating func seek(toRelativeOffset offset: some FixedWidthInteger) - throws + throws(ParsingError) { guard let offset = Int(exactly: offset), (-startPosition...count).contains(offset) @@ -78,7 +80,7 @@ extension ParserSpan { @inlinable @lifetime(&self) public mutating func seek(toAbsoluteOffset offset: some FixedWidthInteger) - throws + throws(ParsingError) { guard let offset = Int(exactly: offset), (0..._bytes.byteCount).contains(offset) @@ -92,7 +94,7 @@ extension ParserSpan { @inlinable @lifetime(&self) public mutating func seek(toOffsetFromEnd offset: some FixedWidthInteger) - throws + throws(ParsingError) { guard let offset = Int(exactly: offset), (0..._bytes.byteCount).contains(offset) diff --git a/Sources/BinaryParsing/Parser Types/Slicing.swift b/Sources/BinaryParsing/Parser Types/Slicing.swift index 4223577..83d6155 100644 --- a/Sources/BinaryParsing/Parser Types/Slicing.swift +++ b/Sources/BinaryParsing/Parser Types/Slicing.swift @@ -12,8 +12,8 @@ extension ParserSpan { @inlinable @lifetime(copy self) - public mutating func sliceSpan(byteCount: some FixedWidthInteger) throws - -> ParserSpan + public mutating func sliceSpan(byteCount: some FixedWidthInteger) + throws(ParsingError) -> ParserSpan { guard let byteCount = Int(exactly: byteCount), count >= 0 else { throw ParsingError(status: .invalidValue, location: startPosition) @@ -29,7 +29,7 @@ extension ParserSpan { public mutating func sliceSpan( objectStride: some FixedWidthInteger, objectCount: some FixedWidthInteger - ) throws -> ParserSpan { + ) throws(ParsingError) -> ParserSpan { guard let objectCount = Int(exactly: objectCount), let objectStride = Int(exactly: objectStride), let byteCount = objectCount *? objectStride, @@ -45,8 +45,8 @@ extension ParserSpan { extension ParserSpan { @inlinable @lifetime(&self) - public mutating func sliceRange(byteCount: some FixedWidthInteger) throws - -> ParserRange + public mutating func sliceRange(byteCount: some FixedWidthInteger) + throws(ParsingError) -> ParserRange { try sliceSpan(byteCount: byteCount).parserRange } @@ -56,7 +56,7 @@ extension ParserSpan { public mutating func sliceRange( objectStride: some FixedWidthInteger, objectCount: some FixedWidthInteger - ) throws -> ParserRange { + ) throws(ParsingError) -> ParserRange { try sliceSpan(objectStride: objectStride, objectCount: objectCount) .parserRange } @@ -66,10 +66,14 @@ extension ParserSpan { @inlinable @lifetime(copy self) @available(macOS 9999, *) - public mutating func sliceUTF8Span(byteCount: some FixedWidthInteger) throws - -> UTF8Span + public mutating func sliceUTF8Span(byteCount: some FixedWidthInteger) + throws(ParsingError) -> UTF8Span { let rawSpan = try sliceSpan(byteCount: byteCount).bytes - return try UTF8Span(validating: Span(_bytes: rawSpan)) + do { + return try UTF8Span(validating: Span(_bytes: rawSpan)) + } catch { + throw ParsingError(status: .userError, location: startPosition) + } } } diff --git a/Sources/BinaryParsing/Parsers/Array.swift b/Sources/BinaryParsing/Parsers/Array.swift index 5fa8945..6dd9bce 100644 --- a/Sources/BinaryParsing/Parsers/Array.swift +++ b/Sources/BinaryParsing/Parsers/Array.swift @@ -12,7 +12,9 @@ extension Array where Element == UInt8 { @inlinable @lifetime(&input) - public init(parsingRemainingBytes input: inout ParserSpan) throws { + public init(parsingRemainingBytes input: inout ParserSpan) + throws(ParsingError) + { defer { _ = input.divide(atOffset: input.count) } self = input.withUnsafeBytes { buffer in Array(buffer) @@ -21,7 +23,9 @@ extension Array where Element == UInt8 { @inlinable @lifetime(&input) - public init(parsing input: inout ParserSpan, byteCount: Int) throws { + public init(parsing input: inout ParserSpan, byteCount: Int) + throws(ParsingError) + { let slice = try input._divide(atByteOffset: byteCount) self = slice.withUnsafeBytes { buffer in Array(buffer) @@ -35,11 +39,13 @@ extension Array { public init( parsing input: inout ParserSpan, count: some FixedWidthInteger, - parser: (inout ParserSpan) throws -> Element - ) throws { + parser: (inout ParserSpan) throws(ThrownParsingError) -> Element + ) throws(ThrownParsingError) { let count = try Int(throwingOnOverflow: count) self = [] self.reserveCapacity(count) + // This doesn't throw (e.g. on empty) because `parser` can produce valid + // values no matter the state of `input`. for _ in 0.. Element - ) throws { + parser: (inout ParserSpan) throws(ThrownParsingError) -> Element + ) throws(ThrownParsingError) { self = [] while !input.isEmpty { try self.append(parser(&input)) diff --git a/Sources/BinaryParsing/Parsers/Integer.swift b/Sources/BinaryParsing/Parsers/Integer.swift index dfb60aa..e7ecc67 100644 --- a/Sources/BinaryParsing/Parsers/Integer.swift +++ b/Sources/BinaryParsing/Parsers/Integer.swift @@ -11,7 +11,7 @@ extension ParserSpan { @inlinable - public func _checkCount(minimum: Int) throws { + public func _checkCount(minimum: Int) throws(ParsingError) { let requiredUpper = _lowerBound &+ minimum guard requiredUpper <= _upperBound else { throw ParsingError( @@ -23,7 +23,7 @@ extension ParserSpan { extension FixedWidthInteger { @_alwaysEmitIntoClient - init(_throwing other: some FixedWidthInteger) throws { + init(_throwing other: some FixedWidthInteger) throws(ParsingError) { guard let newValue = Self(exactly: other) else { throw ParsingError( status: .invalidValue, @@ -47,7 +47,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { @lifetime(&input) init( _parsingBigEndian input: inout ParserSpan - ) throws { + ) throws(ParsingError) { try input._checkCount(minimum: MemoryLayout.size) self.init(_unchecked: (), _parsingBigEndian: &input) } @@ -63,7 +63,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { @inlinable @lifetime(&input) - init(_parsingLittleEndian input: inout ParserSpan) throws { + init(_parsingLittleEndian input: inout ParserSpan) throws(ParsingError) { try input._checkCount(minimum: MemoryLayout.size) self.init(_unchecked: (), _parsingLittleEndian: &input) } @@ -134,10 +134,10 @@ extension FixedWidthInteger where Self: BitwiseCopyable { _parsingSigned input: inout ParserSpan, endianness: Endianness, paddingCount: Int - ) throws { + ) throws(ParsingError) { assert(paddingCount >= 0) - func consumePadding(count: Int) throws -> UInt8 { + func consumePadding(count: Int) throws(ParsingError) -> UInt8 { assert(count > 0) var paddingBuffer = input.divide(atOffset: count) let first = paddingBuffer.consumeUnchecked(type: UInt8.self) @@ -195,10 +195,10 @@ extension FixedWidthInteger where Self: BitwiseCopyable { _parsingUnsigned input: inout ParserSpan, endianness: Endianness, paddingCount: Int - ) throws { + ) throws(ParsingError) { assert(paddingCount >= 0) - func consumeZeroPadding() throws { + func consumeZeroPadding() throws(ParsingError) { var paddingBuffer = input.divide(atOffset: paddingCount) for _ in 0...size if paddingCount < 0 { self = @@ -259,7 +259,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { _parsing input: inout ParserSpan, endianness: Endianness, byteCount: Int - ) throws { + ) throws(ParsingError) { guard byteCount > 0 else { throw ParsingError( status: .invalidValue, @@ -281,7 +281,7 @@ extension MultiByteInteger { @inlinable @lifetime(&input) - public init(parsingBigEndian input: inout ParserSpan) throws { + public init(parsingBigEndian input: inout ParserSpan) throws(ParsingError) { try self.init(_parsingBigEndian: &input) } @@ -293,7 +293,8 @@ extension MultiByteInteger { @inlinable @lifetime(&input) - public init(parsingLittleEndian input: inout ParserSpan) throws { + public init(parsingLittleEndian input: inout ParserSpan) throws(ParsingError) + { try self.init(_parsingLittleEndian: &input) } @@ -310,7 +311,9 @@ extension MultiByteInteger { @inlinable @lifetime(&input) - public init(parsing input: inout ParserSpan, endianness: Endianness) throws { + public init(parsing input: inout ParserSpan, endianness: Endianness) + throws(ParsingError) + { self = try endianness.isBigEndian ? Self(_parsingBigEndian: &input) @@ -326,7 +329,7 @@ extension SingleByteInteger { } @inlinable - public init(parsing input: inout ParserSpan) throws { + public init(parsing input: inout ParserSpan) throws(ParsingError) { guard !input.isEmpty else { throw ParsingError( status: .insufficientData, @@ -341,7 +344,7 @@ extension SingleByteInteger { message: "This initializer should only be used for performance testing." ) @inlinable - public init(parsingUnchecked input: inout ParserSpan) throws { + public init(parsingUnchecked input: inout ParserSpan) throws(ParsingError) { self = input.consumeUnchecked(type: Self.self) } } @@ -351,14 +354,16 @@ extension FixedWidthInteger where Self: BitwiseCopyable { @lifetime(&input) public init( _unchecked _: Void, parsingBigEndian input: inout ParserSpan, byteCount: Int - ) throws { + ) throws(ParsingError) { try self.init( _unchecked: (), _parsing: &input, endianness: .big, byteCount: byteCount) } @inlinable @lifetime(&input) - public init(parsingBigEndian input: inout ParserSpan, byteCount: Int) throws { + public init(parsingBigEndian input: inout ParserSpan, byteCount: Int) + throws(ParsingError) + { try self.init(_parsing: &input, endianness: .big, byteCount: byteCount) } @@ -367,7 +372,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { public init( _unchecked _: Void, parsingLittleEndian input: inout ParserSpan, byteCount: Int - ) throws { + ) throws(ParsingError) { try self.init( _unchecked: (), _parsing: &input, endianness: .little, byteCount: byteCount) @@ -376,7 +381,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { @inlinable @lifetime(&input) public init(parsingLittleEndian input: inout ParserSpan, byteCount: Int) - throws + throws(ParsingError) { try self.init(_parsing: &input, endianness: .little, byteCount: byteCount) } @@ -386,7 +391,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { public init( _unchecked _: Void, parsing input: inout ParserSpan, endianness: Endianness, byteCount: Int - ) throws { + ) throws(ParsingError) { try self.init( _unchecked: (), _parsing: &input, endianness: endianness, byteCount: byteCount) @@ -396,7 +401,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { @lifetime(&input) public init( parsing input: inout ParserSpan, endianness: Endianness, byteCount: Int - ) throws { + ) throws(ParsingError) { try self.init( _parsing: &input, endianness: endianness, byteCount: byteCount) } @@ -407,7 +412,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { _unchecked _: Void, parsing input: inout ParserSpan, storedAsBigEndian: T.Type - ) throws { + ) throws(ParsingError) { let result = T(_unchecked: (), _parsingBigEndian: &input) self = try Self(_throwing: result) } @@ -417,7 +422,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { public init( parsing input: inout ParserSpan, storedAsBigEndian: T.Type - ) throws { + ) throws(ParsingError) { let result = try T(_parsingBigEndian: &input) self = try Self(_throwing: result) } @@ -428,7 +433,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { _unchecked _: Void, parsing input: inout ParserSpan, storedAsLittleEndian: T.Type - ) throws { + ) throws(ParsingError) { let result = T(_unchecked: (), _parsingLittleEndian: &input) self = try Self(_throwing: result) } @@ -438,7 +443,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { public init( parsing input: inout ParserSpan, storedAsLittleEndian: T.Type - ) throws { + ) throws(ParsingError) { let result = try T(_parsingLittleEndian: &input) self = try Self(_throwing: result) } @@ -450,7 +455,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { parsing input: inout ParserSpan, storedAs: T.Type, endianness: Endianness - ) throws { + ) throws(ParsingError) { let result = endianness.isBigEndian ? T(_unchecked: (), _parsingBigEndian: &input) @@ -464,7 +469,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { parsing input: inout ParserSpan, storedAs: T.Type, endianness: Endianness - ) throws { + ) throws(ParsingError) { let result = try endianness.isBigEndian ? T(_parsingBigEndian: &input) @@ -478,7 +483,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { _unchecked _: Void, parsing input: inout ParserSpan, storedAs: T.Type - ) throws { + ) throws(ParsingError) { self = try Self(_throwing: T(truncatingIfNeeded: input.consumeUnchecked())) } @@ -487,7 +492,7 @@ extension FixedWidthInteger where Self: BitwiseCopyable { public init( parsing input: inout ParserSpan, storedAs: T.Type - ) throws { + ) throws(ParsingError) { guard let result = input.consume() else { throw ParsingError( status: .insufficientData, @@ -500,19 +505,22 @@ extension FixedWidthInteger where Self: BitwiseCopyable { extension RawRepresentable where RawValue: MultiByteInteger { @inlinable @lifetime(&input) - public init(parsingBigEndian input: inout ParserSpan) throws { + public init(parsingBigEndian input: inout ParserSpan) throws(ParsingError) { self = try Self(_rawValueThrowing: .init(parsingBigEndian: &input)) } @inlinable @lifetime(&input) - public init(parsingLittleEndian input: inout ParserSpan) throws { + public init(parsingLittleEndian input: inout ParserSpan) throws(ParsingError) + { self = try Self(_rawValueThrowing: .init(parsingLittleEndian: &input)) } @inlinable @lifetime(&input) - public init(parsing input: inout ParserSpan, endianness: Endianness) throws { + public init(parsing input: inout ParserSpan, endianness: Endianness) + throws(ParsingError) + { self = try Self( _rawValueThrowing: .init(parsing: &input, endianness: endianness)) @@ -522,7 +530,7 @@ extension RawRepresentable where RawValue: MultiByteInteger { extension RawRepresentable where RawValue: SingleByteInteger { @inlinable @lifetime(&input) - public init(parsing input: inout ParserSpan) throws { + public init(parsing input: inout ParserSpan) throws(ParsingError) { guard let value = try Self(rawValue: .init(_parsingBigEndian: &input)) else { throw ParsingError( @@ -535,7 +543,7 @@ extension RawRepresentable where RawValue: SingleByteInteger { extension RawRepresentable where RawValue: FixedWidthInteger & BitwiseCopyable { @inlinable - public init(_rawValueThrowing rawValue: RawValue) throws { + public init(_rawValueThrowing rawValue: RawValue) throws(ParsingError) { guard let value = Self(rawValue: rawValue) else { throw ParsingError( status: .invalidValue, @@ -549,7 +557,7 @@ extension RawRepresentable where RawValue: FixedWidthInteger & BitwiseCopyable { public init( parsing input: inout ParserSpan, storedAsBigEndian: T.Type - ) throws { + ) throws(ParsingError) { self = try Self( _rawValueThrowing: .init(parsing: &input, storedAsBigEndian: T.self)) @@ -560,7 +568,7 @@ extension RawRepresentable where RawValue: FixedWidthInteger & BitwiseCopyable { public init( parsing input: inout ParserSpan, storedAsLittleEndian: T.Type - ) throws { + ) throws(ParsingError) { self = try Self( _rawValueThrowing: .init(parsing: &input, storedAsLittleEndian: T.self)) @@ -572,7 +580,7 @@ extension RawRepresentable where RawValue: FixedWidthInteger & BitwiseCopyable { parsing input: inout ParserSpan, storedAs: T.Type, endianness: Endianness - ) throws { + ) throws(ParsingError) { self = try Self( _rawValueThrowing: .init(parsing: &input, storedAs: T.self, endianness: endianness)) @@ -583,7 +591,7 @@ extension RawRepresentable where RawValue: FixedWidthInteger & BitwiseCopyable { public init( parsing input: inout ParserSpan, storedAs: T.Type - ) throws { + ) throws(ParsingError) { self = try Self( _rawValueThrowing: .init(parsing: &input, storedAs: T.self)) diff --git a/Sources/BinaryParsing/Parsers/Range.swift b/Sources/BinaryParsing/Parsers/Range.swift index 78d9c47..53b3439 100644 --- a/Sources/BinaryParsing/Parsers/Range.swift +++ b/Sources/BinaryParsing/Parsers/Range.swift @@ -15,8 +15,8 @@ extension Range where Bound: FixedWidthInteger { @lifetime(&input) public init( parsingStartAndCount input: inout ParserSpan, - parser: (inout ParserSpan) throws -> Bound - ) throws { + parser: (inout ParserSpan) throws(ThrownParsingError) -> Bound + ) throws(ThrownParsingError) { let start = try parser(&input) let count = try parser(&input) guard count >= 0, let end = start +? count else { @@ -37,8 +37,8 @@ extension ClosedRange where Bound: FixedWidthInteger { @lifetime(&input) public init( parsingStartAndCount input: inout ParserSpan, - parser: (inout ParserSpan) throws -> Bound - ) throws { + parser: (inout ParserSpan) throws(ThrownParsingError) -> Bound + ) throws(ThrownParsingError) { let start = try parser(&input) let count = try parser(&input) guard count > 0, let end = start +? count -? 1 else { @@ -56,8 +56,8 @@ extension Range { @lifetime(&input) public init( parsingStartAndEnd input: inout ParserSpan, - boundsParser parser: (inout ParserSpan) throws -> Bound - ) throws { + boundsParser parser: (inout ParserSpan) throws(ThrownParsingError) -> Bound + ) throws(ThrownParsingError) { let start = try parser(&input) let end = try parser(&input) guard start <= end else { @@ -73,8 +73,8 @@ extension ClosedRange { @lifetime(&input) public init( parsingStartAndEnd input: inout ParserSpan, - boundsParser parser: (inout ParserSpan) throws -> Bound - ) throws { + boundsParser parser: (inout ParserSpan) throws(ThrownParsingError) -> Bound + ) throws(ThrownParsingError) { let start = try parser(&input) let end = try parser(&input) guard start <= end else { diff --git a/Sources/BinaryParsing/Parsers/String.swift b/Sources/BinaryParsing/Parsers/String.swift index b8c01d3..3e9aab4 100644 --- a/Sources/BinaryParsing/Parsers/String.swift +++ b/Sources/BinaryParsing/Parsers/String.swift @@ -12,7 +12,8 @@ extension String { @inlinable @lifetime(&input) - public init(parsingNulTerminated input: inout ParserSpan) throws { + public init(parsingNulTerminated input: inout ParserSpan) throws(ParsingError) + { guard let nulOffset = input.withUnsafeBytes({ buffer in buffer.firstIndex(of: 0) @@ -26,7 +27,7 @@ extension String { @inlinable @lifetime(&input) - public init(parsingUTF8 input: inout ParserSpan) throws { + public init(parsingUTF8 input: inout ParserSpan) throws(ParsingError) { let stringBytes = input.divide(at: input.endPosition) self = stringBytes.withUnsafeBytes { buffer in String(decoding: buffer, as: UTF8.self) @@ -35,14 +36,18 @@ extension String { @inlinable @lifetime(&input) - public init(parsingUTF8 input: inout ParserSpan, count: Int) throws { + public init(parsingUTF8 input: inout ParserSpan, count: Int) + throws(ParsingError) + { var slice = try input._divide(atByteOffset: count) try self.init(parsingUTF8: &slice) } @inlinable @lifetime(&input) - internal init(_uncheckedParsingUTF16 input: inout ParserSpan) throws { + internal init(_uncheckedParsingUTF16 input: inout ParserSpan) + throws(ParsingError) + { let stringBytes = input.divide(at: input.endPosition) self = stringBytes.withUnsafeBytes { buffer in let utf16Buffer = buffer.assumingMemoryBound(to: UInt16.self) @@ -52,7 +57,7 @@ extension String { @inlinable @lifetime(&input) - public init(parsingUTF16 input: inout ParserSpan) throws { + public init(parsingUTF16 input: inout ParserSpan) throws(ParsingError) { guard input.count.isMultiple(of: 2) else { throw ParsingError(status: .invalidValue, location: input.startPosition) } @@ -61,7 +66,9 @@ extension String { @inlinable @lifetime(&input) - public init(parsingUTF16 input: inout ParserSpan, codeUnitCount: Int) throws { + public init(parsingUTF16 input: inout ParserSpan, codeUnitCount: Int) + throws(ParsingError) + { var slice = try input._divide( atByteOffset: codeUnitCount.multipliedThrowingOnOverflow(by: 2)) try self.init(_uncheckedParsingUTF16: &slice) diff --git a/Tests/BinaryParsingTests/OptionatorTests.swift b/Tests/BinaryParsingTests/OptionatorTests.swift index e4892d0..835a480 100644 --- a/Tests/BinaryParsingTests/OptionatorTests.swift +++ b/Tests/BinaryParsingTests/OptionatorTests.swift @@ -21,7 +21,7 @@ struct OptionatorTests { let actualInfix = a +? b var actualAssign = a actualAssign +?= b - + switch expected { case (let result, false)?: #expect(actualInfix == result) @@ -31,14 +31,14 @@ struct OptionatorTests { #expect(actualAssign == nil) } } - + @Test(arguments: numbers, numbers) func subtraction(_ a: Int?, _ b: Int?) { let expected = b.flatMap { a?.subtractingReportingOverflow($0) } let actualInfix = a -? b var actualAssign = a actualAssign -?= b - + switch expected { case (let result, false)?: #expect(actualInfix == result) @@ -55,7 +55,7 @@ struct OptionatorTests { let actualInfix = a *? b var actualAssign = a actualAssign *?= b - + switch expected { case (let result, false)?: #expect(actualInfix == result) @@ -72,7 +72,7 @@ struct OptionatorTests { let actualInfix = a /? b var actualAssign = a actualAssign /?= b - + switch expected { case (let result, false)?: #expect(actualInfix == result) @@ -89,7 +89,7 @@ struct OptionatorTests { let actualInfix = a %? b var actualAssign = a actualAssign %?= b - + switch expected { case (let result, false)?: #expect(actualInfix == result) @@ -104,7 +104,7 @@ struct OptionatorTests { func negation(_ a: Int?) { let expected = a?.multipliedReportingOverflow(by: -1) let actual = -?a - + switch expected { case (let result, false)?: #expect(actual == result) @@ -118,7 +118,7 @@ struct OptionatorTests { let actual = a ..