Skip to content

Minor API revisions #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Sources/BinaryParsing/Operations/OptionalOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ extension Collection {
}
return self[i]
}

@inlinable
public subscript(ifInBounds range: Range<Index>) -> SubSequence? {
let bounds = startIndex...endIndex
guard range.lowerBound >= startIndex, range.upperBound <= endIndex
else { return nil }
return self[range]
}
}

extension Collection where Index == Int {
Expand All @@ -27,4 +35,14 @@ extension Collection where Index == Int {
}
return self[i]
}

@_alwaysEmitIntoClient
public subscript<T: FixedWidthInteger>(ifInBounds bounds: Range<T>) -> SubSequence? {
guard let low = Int(exactly: bounds.lowerBound),
let high = Int(exactly: bounds.upperBound),
low >= startIndex, high <= endIndex else {
return nil
}
return self[low..<high]
}
}
9 changes: 9 additions & 0 deletions Sources/BinaryParsing/Operations/ThrowingOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ extension Collection {
return self[i]
}
}

@inlinable
public subscript(throwing bounds: Range<Index>) -> SubSequence {
get throws(ParsingError) {
guard bounds.lowerBound >= startIndex && bounds.upperBound <= endIndex
else { throw ParsingError(statusOnly: .invalidValue) }
return self[bounds]
}
}
}

extension Optional {
Expand Down
4 changes: 4 additions & 0 deletions Sources/BinaryParsing/Parser Types/Endianness.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ extension Endianness {
public var isBigEndian: Bool {
_isBigEndian
}

public var isLittleEndian: Bool {
!_isBigEndian
}
}
14 changes: 0 additions & 14 deletions Sources/BinaryParsing/Parser Types/ParserRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,6 @@ public struct ParserRange: Hashable, Sendable {
}
}

extension ParserRange {
public func slicing<C: Collection<UInt8>>(_ coll: C) throws(ParsingError)
-> C.SubSequence
where C.Index == Int {
let validRange = coll.startIndex...coll.endIndex
guard validRange.contains(range.lowerBound),
validRange.contains(range.upperBound)
else {
throw ParsingError(status: .invalidValue, location: range.lowerBound)
}
return coll[range]
}
}

extension RandomAccessCollection<UInt8> where Index == Int {
public subscript(_ range: ParserRange) -> SubSequence {
get throws(ParsingError) {
Expand Down
4 changes: 2 additions & 2 deletions Sources/BinaryParsing/Parser Types/Seeking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ extension ParserSpan {
throws(ParsingError)
{
guard let offset = Int(exactly: offset),
(-startPosition...count).contains(offset)
(0...count).contains(offset)
else {
throw ParsingError(status: .invalidValue, location: startPosition)
}
Expand All @@ -97,7 +97,7 @@ extension ParserSpan {
throws(ParsingError)
{
guard let offset = Int(exactly: offset),
(0...endPosition).contains(offset)
(0...count).contains(offset)
else {
throw ParsingError(status: .invalidValue, location: startPosition)
}
Expand Down
25 changes: 25 additions & 0 deletions Tests/BinaryParsingTests/EndiannessTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Binary Parsing open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import BinaryParsing
import Testing

struct EndiannessTests {
@Test(arguments: [false, true])
func endianness(isBigEndian: Bool) {
let endianness = Endianness(isBigEndian: isBigEndian)
#expect(endianness.isBigEndian == isBigEndian)
#expect(endianness.isLittleEndian == !isBigEndian)

let endianness2: Endianness = isBigEndian ? .big : .little
#expect(endianness == endianness2)
}
}
33 changes: 26 additions & 7 deletions Tests/BinaryParsingTests/OptionalOperationsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,29 +140,48 @@ struct OptionalOperationsTests {
@Test func collectionIfInBounds() throws {
let str = "Hello, world!"
let substr = str.dropFirst(5).dropLast()

var i = str.startIndex
while true {
if substr.indices.contains(i) {
let allIndices = str.indices + [str.endIndex]
let validIndices = substr.startIndex..<substr.endIndex
let validBounds = substr.startIndex...substr.endIndex

for low in allIndices.indices {
let i = allIndices[low]
if validIndices.contains(i) {
#expect(substr[ifInBounds: i] == substr[i])
} else {
#expect(substr[ifInBounds: i] == nil)
}
if i == str.endIndex { break }
str.formIndex(after: &i)

for high in allIndices[low...].indices {
let j = allIndices[high]
if validBounds.contains(i) && validBounds.contains(j) {
#expect(substr[ifInBounds: i..<j] == substr[i..<j])
} else {
#expect(substr[ifInBounds: i..<j] == nil)
}
}
}
}

@Test func collectionRACIfInBounds() throws {
let numbers = Array(1...100)
let slice = numbers.dropFirst(14).dropLast(20)

let validBounds = UInt8(slice.startIndex)...UInt8(slice.endIndex)

for i in 0...UInt8.max {
if slice.indices.contains(Int(i)) {
#expect(slice[ifInBounds: i] == slice[Int(i)])
} else {
#expect(slice[ifInBounds: i] == nil)
}

for j in i...UInt8.max {
if validBounds.contains(i) && validBounds.contains(j) {
#expect(slice[ifInBounds: i..<j] == slice[Int(i)..<Int(j)])
} else {
#expect(slice[ifInBounds: i..<j] == nil)
}
}
}
}
}
24 changes: 11 additions & 13 deletions Tests/BinaryParsingTests/SeekingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ struct SeekingTests {
let doubleOffsetValue = try Int16(parsingBigEndian: &input)
#expect(doubleOffsetValue == 4)
#expect(input.startPosition == 8)
}

// Start over
try input.seek(toRelativeOffset: -8)
try buffer.withParserSpan { input in
let fourValues = try Array(
parsing: &input,
count: 4,
Expand All @@ -118,11 +118,14 @@ struct SeekingTests {
// Seek to end
try input.seek(toRelativeOffset: 8)
#expect(input.count == 0)
try input.seek(toRelativeOffset: -8)
}

// Can't seek past endpoints
try buffer.withParserSpan { input in
try input.seek(toRelativeOffset: 8)

// Can't seek backwards
#expect(throws: ParsingError.self) {
try input.seek(toRelativeOffset: -9)
try input.seek(toRelativeOffset: -1)
}
#expect(input.startPosition == 8)
#expect(throws: ParsingError.self) {
Expand Down Expand Up @@ -155,11 +158,6 @@ struct SeekingTests {
#expect(value1 == 7)
#expect(input.count == 2)

try input.seek(toOffsetFromEnd: 16)
let value2 = try Int16(parsingBigEndian: &input)
#expect(value2 == 1)
#expect(input.count == 14)

// Can't seek past endpoints
#expect(throws: ParsingError.self) {
try input.seek(toOffsetFromEnd: -1)
Expand All @@ -184,10 +182,10 @@ struct SeekingTests {
#expect(chunk.startPosition == 8)
#expect(chunk.endPosition == 12)
#expect(throws: ParsingError.self) {
try chunk.seek(toOffsetFromEnd: 13)
try chunk.seek(toOffsetFromEnd: 5)
}
try chunk.seek(toOffsetFromEnd: 12)
#expect(chunk.count == 12)
try chunk.seek(toOffsetFromEnd: 4)
#expect(chunk.count == 4)
}
}

Expand Down
12 changes: 12 additions & 0 deletions Tests/BinaryParsingTests/ThrowingOperationsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,17 @@ struct ThrowingOperationsTests {
try Self.numbers[throwing: i]
}
}

let validBounds = Self.numbers.startIndex...Self.numbers.endIndex
for j in i...10 {
if validBounds.contains(i), validBounds.contains(j) {
let result = try Self.numbers[throwing: i..<j]
#expect(Self.numbers[i..<j] == result)
} else {
#expect(throws: ParsingError.self) {
try Self.numbers[throwing: i..<j]
}
}
}
}
}