Skip to content
24 changes: 21 additions & 3 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions EmbeddedExample/EmbeddedParser.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
49 changes: 39 additions & 10 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ let package = Package(
],
products: [
.library(name: "BinaryParsing", targets: ["BinaryParsing"])
// .library(name: "BinaryParsingEmbedded", targets: ["BinaryParsingEmbedded"]),
],
dependencies: [
.package(
Expand All @@ -43,14 +42,6 @@ let package = Package(
.strictMemorySafety(),
]
),
// .target(
// name: "BinaryParsingEmbedded",
// dependencies: ["BinaryParsingMacros"],
// swiftSettings: [
// .enableExperimentalFeature("Embedded"),
// .enableExperimentalFeature("Lifetimes"),
// ]
// ),
.macro(
name: "BinaryParsingMacros",
dependencies: [
Expand Down Expand Up @@ -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",
Expand All @@ -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
}
10 changes: 6 additions & 4 deletions Sources/BinaryParsing/Parser Types/ParserSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//
//===----------------------------------------------------------------------===//

#if canImport(Foundation)
#if !$Embedded && canImport(Foundation)
public import Foundation
#endif

Expand Down Expand Up @@ -65,7 +65,7 @@ extension RandomAccessCollection<UInt8> {
public func withParserSpanIfAvailable<T>(
_ 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)
Expand All @@ -81,7 +81,9 @@ extension RandomAccessCollection<UInt8> {
let result = self.withContiguousStorageIfAvailable { buffer in
let rawBuffer = UnsafeRawBufferPointer(buffer)
var span = unsafe ParserSpan(_unsafeBytes: rawBuffer)
return Result<T, ThrownParsingError> { try body(&span) }
return Result<T, ThrownParsingError> { () throws(ThrownParsingError) in
try body(&span)
}
}
switch result {
case .success(let t): return t
Expand Down Expand Up @@ -136,7 +138,7 @@ extension ParserSpanProvider {
}
}

#if canImport(Foundation)
#if !$Embedded && canImport(Foundation)
extension Data: ParserSpanProvider {
@inlinable
public func withParserSpan<T, E>(
Expand Down
6 changes: 3 additions & 3 deletions Sources/BinaryParsing/Parsers/Array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/BinaryParsing/Parsers/Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//
//===----------------------------------------------------------------------===//

#if canImport(Foundation)
#if !$Embedded && canImport(Foundation)
public import Foundation

extension Data {
Expand Down
37 changes: 36 additions & 1 deletion Sources/BinaryParsing/Parsers/InlineArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] }
Expand All @@ -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.
///
Expand Down Expand Up @@ -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)
}
}
}
17 changes: 17 additions & 0 deletions Sources/BinaryParsing/Support/LinuxRNGSupport.swift
Original file line number Diff line number Diff line change
@@ -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