diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4eb62c2..e18e241 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,11 +27,29 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: - macos_xcode_versions: "[\"26.1\"]" - linux_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}, {\"swift_version\": \"6.0\"}, {\"swift_version\": \"6.1\"}]" enable_macos_checks: true - enable_windows_checks: false + macos_xcode_versions: "[\"26.1\"]" + linux_swift_versions: "[ \"6.2\", \"nightly-6.3\"]" linux_build_command: swift test --disable-experimental-prebuilts # prebuilt swift-syntax not building + enable_windows_checks: false + + embedded: + name: Embedded Test (Linux nightly-6.3) + runs-on: ubuntu-latest + container: + image: swiftlang/swift:nightly-6.3-jammy + steps: + - name: Swift version + run: swift --version + - name: Clang version + run: clang --version + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build / Test + run: | + ENABLE_EMBEDDED=1 swift test --disable-experimental-prebuilts + echo "Running embedded Swift executable..." + .build/debug/EmbeddedExample soundness: name: Soundness diff --git a/EmbeddedExample/EmbeddedParser.swift b/EmbeddedExample/EmbeddedParser.swift new file mode 100644 index 0000000..4df6658 --- /dev/null +++ b/EmbeddedExample/EmbeddedParser.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Binary Parsing open source project +// +// Copyright (c) 2026 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 BinaryParsingEmbedded + +@available(macOS 26.0, *) +@main +struct EmbeddedTest { + static func main() throws(ParsingError) { + let data: InlineArray<_, UInt8> = [0x0, 0x1, 0x2, 0x3, 0x4, 0x5] + var span = ParserSpan(data.span.bytes) + + let a = try Int16(parsingBigEndian: &span) + let b = try Int16(parsingBigEndian: &span) + let c = try Int16(parsingBigEndian: &span) + + precondition(a == 1) + precondition(b == 0x203) + print(c) + } +} diff --git a/Package.swift b/Package.swift index f4e3bc0..ba46203 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,6 @@ let package = Package( ], products: [ .library(name: "BinaryParsing", targets: ["BinaryParsing"]) - // .library(name: "BinaryParsingEmbedded", targets: ["BinaryParsingEmbedded"]), ], dependencies: [ .package( @@ -43,14 +42,6 @@ let package = Package( .strictMemorySafety(), ] ), - // .target( - // name: "BinaryParsingEmbedded", - // dependencies: ["BinaryParsingMacros"], - // swiftSettings: [ - // .enableExperimentalFeature("Embedded"), - // .enableExperimentalFeature("Lifetimes"), - // ] - // ), .macro( name: "BinaryParsingMacros", dependencies: [ @@ -119,7 +110,9 @@ let package = Package( ] ) -if ProcessInfo.processInfo.environment["ENABLE_BENCHMARKING"] != nil { +// MARK: Benchmarking overrides + +if isSet("ENABLE_BENCHMARKING") { package.dependencies += [ .package( url: "https://github.com/ordo-one/package-benchmark", @@ -141,3 +134,39 @@ if ProcessInfo.processInfo.environment["ENABLE_BENCHMARKING"] != nil { ) ] } + +// MARK: Embedded overrides + +if isSet("ENABLE_EMBEDDED") { + package.platforms = [ + .macOS(.v14), .iOS(.v17), .watchOS(.v10), .tvOS(.v17), .visionOS(.v1), + ] + + package.products += [ + .library(name: "BinaryParsingEmbedded", targets: ["BinaryParsingEmbedded"]) + ] + + package.targets += [ + .target( + name: "BinaryParsingEmbedded", + dependencies: ["BinaryParsingMacros"], + swiftSettings: [ + .enableExperimentalFeature("Embedded"), + .enableExperimentalFeature("Lifetimes"), + .strictMemorySafety(), + ] + ), + .executableTarget( + name: "EmbeddedExample", + dependencies: ["BinaryParsingEmbedded"], + path: "EmbeddedExample", + swiftSettings: [ + .enableExperimentalFeature("Embedded") + ] + ), + ] +} + +func isSet(_ key: String) -> Bool { + ProcessInfo.processInfo.environment[key] != nil +} diff --git a/Sources/BinaryParsing/Parser Types/ParserSource.swift b/Sources/BinaryParsing/Parser Types/ParserSource.swift index 05f5f21..d6e02fc 100644 --- a/Sources/BinaryParsing/Parser Types/ParserSource.swift +++ b/Sources/BinaryParsing/Parser Types/ParserSource.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(Foundation) +#if !$Embedded && canImport(Foundation) public import Foundation #endif @@ -65,7 +65,7 @@ extension RandomAccessCollection { public func withParserSpanIfAvailable( _ body: (inout ParserSpan) throws(ThrownParsingError) -> T ) throws(ThrownParsingError) -> T? { - #if canImport(Foundation) + #if !$Embedded && canImport(Foundation) if let data = self as? Foundation.Data { let result = unsafe data.withUnsafeBytes { buffer in var span = unsafe ParserSpan(_unsafeBytes: buffer) @@ -81,7 +81,9 @@ extension RandomAccessCollection { let result = self.withContiguousStorageIfAvailable { buffer in let rawBuffer = UnsafeRawBufferPointer(buffer) var span = unsafe ParserSpan(_unsafeBytes: rawBuffer) - return Result { try body(&span) } + return Result { () throws(ThrownParsingError) in + try body(&span) + } } switch result { case .success(let t): return t @@ -136,7 +138,7 @@ extension ParserSpanProvider { } } -#if canImport(Foundation) +#if !$Embedded && canImport(Foundation) extension Data: ParserSpanProvider { @inlinable public func withParserSpan( diff --git a/Sources/BinaryParsing/Parsers/Array.swift b/Sources/BinaryParsing/Parsers/Array.swift index 678aa22..86d84d0 100644 --- a/Sources/BinaryParsing/Parsers/Array.swift +++ b/Sources/BinaryParsing/Parsers/Array.swift @@ -91,7 +91,7 @@ extension Array { #endif /// Creates a new array by parsing the specified number of elements from the given - /// parser span, using the provided closure for parsing. + /// parser span, using the provided closure for parsing (with a typed error). /// /// The provided closure is called `byteCount` times while initializing the array. /// For example, the following code parses an array of 16 `UInt32` values from a @@ -119,8 +119,8 @@ extension Array { public init( parsing input: inout ParserSpan, count: Int, - parser: (inout ParserSpan) throws(ThrownParsingError) -> Element - ) throws(ThrownParsingError) { + parser: (inout ParserSpan) throws(ParsingError) -> Element + ) throws(ParsingError) { guard count >= 0 else { throw ParsingError(statusOnly: .invalidValue) } diff --git a/Sources/BinaryParsing/Parsers/Data.swift b/Sources/BinaryParsing/Parsers/Data.swift index 85836a1..fabf1dd 100644 --- a/Sources/BinaryParsing/Parsers/Data.swift +++ b/Sources/BinaryParsing/Parsers/Data.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(Foundation) +#if !$Embedded && canImport(Foundation) public import Foundation extension Data { diff --git a/Sources/BinaryParsing/Parsers/InlineArray.swift b/Sources/BinaryParsing/Parsers/InlineArray.swift index 2fb49bd..b87c88a 100644 --- a/Sources/BinaryParsing/Parsers/InlineArray.swift +++ b/Sources/BinaryParsing/Parsers/InlineArray.swift @@ -18,7 +18,7 @@ extension InlineArray where Element == UInt8 { /// - Throws: A `ParsingError` if `input` does not have at least `count` /// bytes remaining. @inlinable - public init(parsing input: inout ParserSpan) throws { + public init(parsing input: inout ParserSpan) throws(ParsingError) { let slice = try input._divide(atByteOffset: Self.count) self = unsafe slice.withUnsafeBytes { buffer in InlineArray { unsafe buffer[$0] } @@ -28,6 +28,7 @@ extension InlineArray where Element == UInt8 { @available(macOS 26, iOS 26, watchOS 26, tvOS 26, visionOS 26, *) extension InlineArray where Element: ~Copyable { + #if !$Embedded /// Creates a new array by parsing the specified number of elements from the given /// parser span, using the provided closure for parsing. /// @@ -60,4 +61,38 @@ extension InlineArray where Element: ~Copyable { try parser(&input) } } + #endif + + /// Creates a new array by parsing the specified number of elements from the given + /// parser span, using the provided closure for parsing (with a typed error). + /// + /// The provided closure is called `count` times while initializing the inline array. + /// For example, the following code parses a 16-element `InlineArray` of `UInt32` + /// values from a `ParserSpan`. If the `input` parser span doesn't represent enough + /// memory for those 16 values, the call will throw a `ParsingError`. + /// + /// let integers = try InlineArray<16, UInt32>(parsing: &input) { input in + /// try UInt32(parsingBigEndian: &input) + /// } + /// + /// You can also pass a parser initializer to this initializer as a value, if it has + /// the correct shape: + /// + /// let integers = try InlineArray<16, UInt32>( + /// parsing: &input, + /// parser: UInt32.init(parsingBigEndian:)) + /// + /// - Parameters: + /// - input: The `ParserSpan` to consume. + /// - parser: A closure that parses each element from `input`. + /// - Throws: An error if one is thrown from `parser`. + @inlinable + public init( + parsing input: inout ParserSpan, + parser: (inout ParserSpan) throws(ParsingError) -> Element + ) throws(ParsingError) { + self = try InlineArray { _ throws(ParsingError) in + try parser(&input) + } + } } diff --git a/Sources/BinaryParsing/Support/LinuxRNGSupport.swift b/Sources/BinaryParsing/Support/LinuxRNGSupport.swift new file mode 100644 index 0000000..81b58c5 --- /dev/null +++ b/Sources/BinaryParsing/Support/LinuxRNGSupport.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if $Embedded && os(Linux) +// Temporary workaround for lack of random support on embedded Linux +// swift-format-ignore: AlwaysUseLowerCamelCase +@_cdecl("arc4random_buf") +func arc4random_buf(_ buf: UnsafeMutableRawPointer, _ nbytes: Int) {} +#endif