Skip to content

Commit 4c66682

Browse files
authored
Minor API revisions (swiftlang#12)
* Restrict relative seeking a bit further This change means that the `seek(toRelativeOffset:)` and `seek(toOffsetFromEnd:)` are restricted to movement _within_ the current range of the span. This is how these are used, and is a bit easier to explain because movement is therefore bound by both ends of the span. The `seek(toAbsoluteOffset:)` method still works with the original, un-shrunken bounds, so has a different set of semantics (and may benefit from a different name). * Eliminate ParserRange.slicing This API is the mirror image of the Collection subscript that is also provided, and is the less natural way to spell the operation. * Add range-based optional and throwing subscripts These should be consistent with the element-based subscripts. * Complete the Endianness API and add tests
1 parent cba000e commit 4c66682

File tree

9 files changed

+107
-36
lines changed

9 files changed

+107
-36
lines changed

Sources/BinaryParsing/Operations/OptionalOperations.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ extension Collection {
1717
}
1818
return self[i]
1919
}
20+
21+
@inlinable
22+
public subscript(ifInBounds range: Range<Index>) -> SubSequence? {
23+
let bounds = startIndex...endIndex
24+
guard range.lowerBound >= startIndex, range.upperBound <= endIndex
25+
else { return nil }
26+
return self[range]
27+
}
2028
}
2129

2230
extension Collection where Index == Int {
@@ -27,4 +35,14 @@ extension Collection where Index == Int {
2735
}
2836
return self[i]
2937
}
38+
39+
@_alwaysEmitIntoClient
40+
public subscript<T: FixedWidthInteger>(ifInBounds bounds: Range<T>) -> SubSequence? {
41+
guard let low = Int(exactly: bounds.lowerBound),
42+
let high = Int(exactly: bounds.upperBound),
43+
low >= startIndex, high <= endIndex else {
44+
return nil
45+
}
46+
return self[low..<high]
47+
}
3048
}

Sources/BinaryParsing/Operations/ThrowingOperations.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ extension Collection {
1919
return self[i]
2020
}
2121
}
22+
23+
@inlinable
24+
public subscript(throwing bounds: Range<Index>) -> SubSequence {
25+
get throws(ParsingError) {
26+
guard bounds.lowerBound >= startIndex && bounds.upperBound <= endIndex
27+
else { throw ParsingError(statusOnly: .invalidValue) }
28+
return self[bounds]
29+
}
30+
}
2231
}
2332

2433
extension Optional {

Sources/BinaryParsing/Parser Types/Endianness.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,8 @@ extension Endianness {
2929
public var isBigEndian: Bool {
3030
_isBigEndian
3131
}
32+
33+
public var isLittleEndian: Bool {
34+
!_isBigEndian
35+
}
3236
}

Sources/BinaryParsing/Parser Types/ParserRange.swift

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,6 @@ public struct ParserRange: Hashable, Sendable {
3434
}
3535
}
3636

37-
extension ParserRange {
38-
public func slicing<C: Collection<UInt8>>(_ coll: C) throws(ParsingError)
39-
-> C.SubSequence
40-
where C.Index == Int {
41-
let validRange = coll.startIndex...coll.endIndex
42-
guard validRange.contains(range.lowerBound),
43-
validRange.contains(range.upperBound)
44-
else {
45-
throw ParsingError(status: .invalidValue, location: range.lowerBound)
46-
}
47-
return coll[range]
48-
}
49-
}
50-
5137
extension RandomAccessCollection<UInt8> where Index == Int {
5238
public subscript(_ range: ParserRange) -> SubSequence {
5339
get throws(ParsingError) {

Sources/BinaryParsing/Parser Types/Seeking.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ extension ParserSpan {
7070
throws(ParsingError)
7171
{
7272
guard let offset = Int(exactly: offset),
73-
(-startPosition...count).contains(offset)
73+
(0...count).contains(offset)
7474
else {
7575
throw ParsingError(status: .invalidValue, location: startPosition)
7676
}
@@ -97,7 +97,7 @@ extension ParserSpan {
9797
throws(ParsingError)
9898
{
9999
guard let offset = Int(exactly: offset),
100-
(0...endPosition).contains(offset)
100+
(0...count).contains(offset)
101101
else {
102102
throw ParsingError(status: .invalidValue, location: startPosition)
103103
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Binary Parsing open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import BinaryParsing
13+
import Testing
14+
15+
struct EndiannessTests {
16+
@Test(arguments: [false, true])
17+
func endianness(isBigEndian: Bool) {
18+
let endianness = Endianness(isBigEndian: isBigEndian)
19+
#expect(endianness.isBigEndian == isBigEndian)
20+
#expect(endianness.isLittleEndian == !isBigEndian)
21+
22+
let endianness2: Endianness = isBigEndian ? .big : .little
23+
#expect(endianness == endianness2)
24+
}
25+
}

Tests/BinaryParsingTests/OptionalOperationsTests.swift

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,29 +140,48 @@ struct OptionalOperationsTests {
140140
@Test func collectionIfInBounds() throws {
141141
let str = "Hello, world!"
142142
let substr = str.dropFirst(5).dropLast()
143-
144-
var i = str.startIndex
145-
while true {
146-
if substr.indices.contains(i) {
143+
let allIndices = str.indices + [str.endIndex]
144+
let validIndices = substr.startIndex..<substr.endIndex
145+
let validBounds = substr.startIndex...substr.endIndex
146+
147+
for low in allIndices.indices {
148+
let i = allIndices[low]
149+
if validIndices.contains(i) {
147150
#expect(substr[ifInBounds: i] == substr[i])
148151
} else {
149152
#expect(substr[ifInBounds: i] == nil)
150153
}
151-
if i == str.endIndex { break }
152-
str.formIndex(after: &i)
154+
155+
for high in allIndices[low...].indices {
156+
let j = allIndices[high]
157+
if validBounds.contains(i) && validBounds.contains(j) {
158+
#expect(substr[ifInBounds: i..<j] == substr[i..<j])
159+
} else {
160+
#expect(substr[ifInBounds: i..<j] == nil)
161+
}
162+
}
153163
}
154164
}
155165

156166
@Test func collectionRACIfInBounds() throws {
157167
let numbers = Array(1...100)
158168
let slice = numbers.dropFirst(14).dropLast(20)
159-
169+
let validBounds = UInt8(slice.startIndex)...UInt8(slice.endIndex)
170+
160171
for i in 0...UInt8.max {
161172
if slice.indices.contains(Int(i)) {
162173
#expect(slice[ifInBounds: i] == slice[Int(i)])
163174
} else {
164175
#expect(slice[ifInBounds: i] == nil)
165176
}
177+
178+
for j in i...UInt8.max {
179+
if validBounds.contains(i) && validBounds.contains(j) {
180+
#expect(slice[ifInBounds: i..<j] == slice[Int(i)..<Int(j)])
181+
} else {
182+
#expect(slice[ifInBounds: i..<j] == nil)
183+
}
184+
}
166185
}
167186
}
168187
}

Tests/BinaryParsingTests/SeekingTests.swift

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ struct SeekingTests {
105105
let doubleOffsetValue = try Int16(parsingBigEndian: &input)
106106
#expect(doubleOffsetValue == 4)
107107
#expect(input.startPosition == 8)
108+
}
108109

109-
// Start over
110-
try input.seek(toRelativeOffset: -8)
110+
try buffer.withParserSpan { input in
111111
let fourValues = try Array(
112112
parsing: &input,
113113
count: 4,
@@ -118,11 +118,14 @@ struct SeekingTests {
118118
// Seek to end
119119
try input.seek(toRelativeOffset: 8)
120120
#expect(input.count == 0)
121-
try input.seek(toRelativeOffset: -8)
121+
}
122122

123-
// Can't seek past endpoints
123+
try buffer.withParserSpan { input in
124+
try input.seek(toRelativeOffset: 8)
125+
126+
// Can't seek backwards
124127
#expect(throws: ParsingError.self) {
125-
try input.seek(toRelativeOffset: -9)
128+
try input.seek(toRelativeOffset: -1)
126129
}
127130
#expect(input.startPosition == 8)
128131
#expect(throws: ParsingError.self) {
@@ -155,11 +158,6 @@ struct SeekingTests {
155158
#expect(value1 == 7)
156159
#expect(input.count == 2)
157160

158-
try input.seek(toOffsetFromEnd: 16)
159-
let value2 = try Int16(parsingBigEndian: &input)
160-
#expect(value2 == 1)
161-
#expect(input.count == 14)
162-
163161
// Can't seek past endpoints
164162
#expect(throws: ParsingError.self) {
165163
try input.seek(toOffsetFromEnd: -1)
@@ -184,10 +182,10 @@ struct SeekingTests {
184182
#expect(chunk.startPosition == 8)
185183
#expect(chunk.endPosition == 12)
186184
#expect(throws: ParsingError.self) {
187-
try chunk.seek(toOffsetFromEnd: 13)
185+
try chunk.seek(toOffsetFromEnd: 5)
188186
}
189-
try chunk.seek(toOffsetFromEnd: 12)
190-
#expect(chunk.count == 12)
187+
try chunk.seek(toOffsetFromEnd: 4)
188+
#expect(chunk.count == 4)
191189
}
192190
}
193191

Tests/BinaryParsingTests/ThrowingOperationsTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,17 @@ struct ThrowingOperationsTests {
159159
try Self.numbers[throwing: i]
160160
}
161161
}
162+
163+
let validBounds = Self.numbers.startIndex...Self.numbers.endIndex
164+
for j in i...10 {
165+
if validBounds.contains(i), validBounds.contains(j) {
166+
let result = try Self.numbers[throwing: i..<j]
167+
#expect(Self.numbers[i..<j] == result)
168+
} else {
169+
#expect(throws: ParsingError.self) {
170+
try Self.numbers[throwing: i..<j]
171+
}
172+
}
173+
}
162174
}
163175
}

0 commit comments

Comments
 (0)