-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit abfa06c
Showing
6 changed files
with
552 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/configuration/registries.json | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.netrc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"originHash" : "c8b42ad06f220997e6d7b3ee99605e794ea71f5ca77f24e6421f398e385119ae", | ||
"pins" : [ | ||
{ | ||
"identity" : "swift-syntax", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-syntax.git", | ||
"state" : { | ||
"revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd", | ||
"version" : "510.0.1" | ||
} | ||
} | ||
], | ||
"version" : 3 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// swift-tools-version: 5.10 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
import CompilerPluginSupport | ||
|
||
let package = Package( | ||
name: "SwiftBin", | ||
platforms: [ | ||
.macOS(.v10_15), | ||
], | ||
products: [ | ||
// Products define the executables and libraries a package produces, making them visible to other packages. | ||
.library( | ||
name: "SwiftBin", | ||
targets: ["SwiftBin"]), | ||
], | ||
dependencies: [ | ||
// Dependencies declare other packages that this package depends on. | ||
.package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.0-latest"), | ||
], | ||
targets: [ | ||
// Targets are the basic building blocks of a package, defining a module or a test suite. | ||
// Targets can depend on other targets in this package and products from dependencies. | ||
.macro( | ||
name: "SwiftBinMacros", | ||
dependencies: [ | ||
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"), | ||
.product(name: "SwiftCompilerPlugin", package: "swift-syntax") | ||
] | ||
), | ||
.target( | ||
name: "SwiftBin", | ||
dependencies: ["SwiftBinMacros"] | ||
), | ||
.testTarget( | ||
name: "SwiftBinTests", | ||
dependencies: ["SwiftBin"] | ||
), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
public struct BinaryParsingNeedsMoreDataError: Error {} | ||
|
||
@attached(member, names: named(init), named(serialize)) | ||
@attached(extension, conformances: BinaryFormatProtocol) | ||
public macro BinaryFormat() = #externalMacro(module: "SwiftBinMacros", type: "BinaryFormatMacro") | ||
|
||
@attached(member, names: named(init), named(serialize), named(Marker)) | ||
@attached(extension, conformances: BinaryEnumProtocol) | ||
public macro BinaryEnum() = #externalMacro(module: "SwiftBinMacros", type: "BinaryEnumMacro") | ||
|
||
@attached(member, names: named(init), named(serialize), named(Marker)) | ||
@attached(extension, conformances: BinaryNonFrozenEnumProtocol) | ||
public macro OpenBinaryEnum() = #externalMacro(module: "SwiftBinMacros", type: "BinaryEnumMacro") | ||
|
||
public enum BinarySerializationError: Error { | ||
case lengthDoesNotFit | ||
} | ||
|
||
public enum BinaryParsingError: Error { | ||
case invalidOrUnknownEnumValue | ||
} | ||
|
||
public struct BinaryBuffer: ~Copyable { | ||
internal var pointer: UnsafePointer<UInt8> | ||
internal var count: Int | ||
private let release: (() -> Void)? | ||
|
||
public typealias ReleaseCallback = () -> Void | ||
|
||
public init(pointer: UnsafePointer<UInt8>, count: Int, release: ReleaseCallback?) { | ||
self.pointer = pointer | ||
self.count = count | ||
self.release = release | ||
} | ||
|
||
private mutating func advance(by length: Int) { | ||
pointer += length | ||
count -= length | ||
} | ||
|
||
internal mutating func readInteger<F: FixedWidthInteger>(_ type: F.Type = F.self) throws -> F { | ||
let size = MemoryLayout<F>.size | ||
if count < size { | ||
throw BinaryParsingNeedsMoreDataError() | ||
} | ||
|
||
let value = pointer.withMemoryRebound(to: F.self, capacity: 1) { $0.pointee } | ||
advance(by: size) | ||
return value | ||
} | ||
|
||
internal mutating func readWithBuffer<T>(length: Int, parse: (inout BinaryBuffer) throws -> T) throws -> T { | ||
guard count >= length else { | ||
throw BinaryParsingNeedsMoreDataError() | ||
} | ||
|
||
var buffer = BinaryBuffer(pointer: pointer, count: length, release: nil) | ||
let value = try parse(&buffer) | ||
advance(by: length) | ||
return value | ||
} | ||
|
||
internal mutating func readString(length: Int) throws -> String { | ||
try readWithBuffer(length: length) { buffer in | ||
buffer.getString() | ||
} | ||
} | ||
|
||
internal mutating func withConsumedBuffer<T>( | ||
parse: (UnsafeBufferPointer<UInt8>) throws -> T | ||
) rethrows -> T { | ||
let value = try parse(UnsafeBufferPointer(start: pointer, count: count)) | ||
advance(by: count) | ||
return value | ||
} | ||
|
||
internal mutating func getString() -> String { | ||
withConsumedBuffer { buffer in | ||
String(decoding: buffer, as: UTF8.self) | ||
} | ||
} | ||
|
||
deinit { release?() } | ||
} | ||
|
||
public enum Endianness { | ||
case little, big | ||
|
||
@inlinable | ||
internal func convert<F: FixedWidthInteger>(_ integer: F) -> F { | ||
switch self { | ||
case .little: return integer.littleEndian | ||
case .big: return integer.bigEndian | ||
} | ||
} | ||
} | ||
|
||
public struct BinaryWriter: ~Copyable { | ||
public typealias WriteCallback = (UnsafeRawBufferPointer) throws -> Void | ||
|
||
public var defaultEndianness: Endianness | ||
|
||
@usableFromInline | ||
internal let write: WriteCallback | ||
|
||
public init(defaultEndianness: Endianness, write: @escaping WriteCallback) { | ||
self.defaultEndianness = defaultEndianness | ||
self.write = write | ||
} | ||
|
||
@inlinable | ||
public mutating func writeInteger<F: FixedWidthInteger>(_ integer: F, endianness: Endianness? = nil) throws { | ||
let endianness = endianness ?? defaultEndianness | ||
let converted = endianness.convert(integer) | ||
try withUnsafeBytes(of: converted, write) | ||
} | ||
|
||
@inlinable | ||
public mutating func writeBytes(_ pointer: UnsafePointer<UInt8>, size: Int) throws { | ||
try write(UnsafeRawBufferPointer(start: pointer, count: size)) | ||
} | ||
|
||
@inlinable | ||
public mutating func writeString(_ string: String) throws { | ||
try writeBytes(string, size: string.utf8.count) | ||
} | ||
} | ||
|
||
public enum BinaryParsingResult<T> { | ||
case parsed(T) | ||
case needsMoreData | ||
|
||
public func map<N>(_ map: (T) -> N) -> BinaryParsingResult<N> { | ||
switch self { | ||
case .parsed(let value): return .parsed(map(value)) | ||
case .needsMoreData: return .needsMoreData | ||
} | ||
} | ||
} | ||
|
||
public protocol BinaryFormatProtocol { | ||
init(consuming buffer: inout BinaryBuffer) throws | ||
func serialize(into writer: inout BinaryWriter) throws | ||
} | ||
|
||
public protocol BinaryEnumProtocol: BinaryFormatProtocol { | ||
// associatedtype Marker: RawRepresentable where Marker.RawValue: FixedWidthInteger | ||
} | ||
|
||
public protocol BinaryNonFrozenEnumProtocol: BinaryEnumProtocol { | ||
static var unknown: Self { get } | ||
} | ||
|
||
public protocol BinaryFormatWithLength: BinaryFormatProtocol { | ||
var byteSize: Int { get } | ||
} | ||
|
||
@propertyWrapper public struct LengthEncoded<Length: FixedWidthInteger, Value: BinaryFormatWithLength>: BinaryFormatProtocol { | ||
public var wrappedValue: Value | ||
public var projectedValue: Self { self } | ||
public init(wrappedValue: Value) { | ||
self.wrappedValue = wrappedValue | ||
} | ||
|
||
public init(consuming buffer: inout BinaryBuffer) throws { | ||
let length = try Int(buffer.readInteger(Length.self)) | ||
guard buffer.count >= length else { | ||
throw BinaryParsingNeedsMoreDataError() | ||
} | ||
|
||
self.wrappedValue = try buffer.readWithBuffer(length: length) { buffer in | ||
try Value(consuming: &buffer) | ||
} | ||
} | ||
|
||
public func serialize(into writer: inout BinaryWriter) throws { | ||
let byteSize = wrappedValue.byteSize | ||
|
||
guard byteSize <= Length.max else { | ||
throw BinarySerializationError.lengthDoesNotFit | ||
} | ||
|
||
try writer.writeInteger(Length(byteSize)) | ||
try wrappedValue.serialize(into: &writer) | ||
} | ||
} | ||
|
||
extension FixedWidthInteger where Self: BinaryFormatProtocol { | ||
public func serialize(into writer: inout BinaryWriter) throws { | ||
try writer.writeInteger(self) | ||
} | ||
|
||
public init(consuming buffer: inout BinaryBuffer) throws { | ||
self = try buffer.readInteger() | ||
} | ||
} | ||
|
||
extension Int: BinaryFormatProtocol {} | ||
extension Int8: BinaryFormatProtocol {} | ||
extension Int16: BinaryFormatProtocol {} | ||
extension Int32: BinaryFormatProtocol {} | ||
extension Int64: BinaryFormatProtocol {} | ||
extension UInt: BinaryFormatProtocol {} | ||
extension UInt8: BinaryFormatProtocol {} | ||
extension UInt16: BinaryFormatProtocol {} | ||
extension UInt32: BinaryFormatProtocol {} | ||
extension UInt64: BinaryFormatProtocol {} | ||
|
||
extension RawRepresentable where Self: BinaryFormatProtocol, RawValue: FixedWidthInteger & BinaryFormatProtocol { | ||
public func serialize(into writer: inout BinaryWriter) throws { | ||
try writer.writeInteger(rawValue) | ||
} | ||
|
||
public init(consuming buffer: inout BinaryBuffer) throws { | ||
let number = try buffer.readInteger(RawValue.self) | ||
guard let value = Self(rawValue: number) else { | ||
throw BinaryParsingError.invalidOrUnknownEnumValue | ||
} | ||
|
||
self = value | ||
} | ||
} | ||
|
||
extension Double: BinaryFormatProtocol { | ||
public func serialize(into writer: inout BinaryWriter) throws { | ||
try writer.writeInteger(bitPattern) | ||
} | ||
|
||
public init(consuming buffer: inout BinaryBuffer) throws { | ||
try self.init(bitPattern: UInt64(consuming: &buffer)) | ||
} | ||
} | ||
|
||
extension Float: BinaryFormatProtocol { | ||
public func serialize(into writer: inout BinaryWriter) throws { | ||
try writer.writeInteger(bitPattern) | ||
} | ||
|
||
public init(consuming buffer: inout BinaryBuffer) throws { | ||
try self.init(bitPattern: UInt32(consuming: &buffer)) | ||
} | ||
} | ||
|
||
import Foundation | ||
|
||
extension Data: BinaryFormatWithLength { | ||
public var byteSize: Int { count } | ||
public func serialize(into writer: inout BinaryWriter) throws { | ||
try withUnsafeBytes { buffer in | ||
try writer.writeBytes( | ||
buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), | ||
size: buffer.count | ||
) | ||
} | ||
} | ||
|
||
public init(consuming buffer: inout BinaryBuffer) throws { | ||
self = buffer.withConsumedBuffer { buffer in | ||
Data(bytes: buffer.baseAddress!, count: buffer.count) | ||
} | ||
} | ||
} |
Oops, something went wrong.