[Swift] Flexbuffers native swift port (#8577)

* Offical Swift port for FlexBuffers

This is the offical port for FlexBuffers within
swift, and it introcudes a Common Module where code
is shared between flatbuffers and flexbuffers.

Writing most supported values like maps, vectors,
nil and scalars into a flexbuffer buffer. And includes
tests to verify that its similar to cpp

* Reading a flexbuffer

Implementing reading from a flexbuffer, enabling
most of the buffers features, like most types, maps, vectors,
typedvectors, and fixedtypedvectors.

Currently, if an offset/object cant be read we default to a swift
nil instead of the default flexbuffers 'null' with all values.

* Fixes bazel breaking due to new project structure

Address warnings within the library

* Adds comment on why we added the code & properly enforce the amout of bytes needed
This commit is contained in:
mustiikhalil
2025-06-22 08:36:38 +02:00
committed by GitHub
parent 595ac94a6a
commit 5a95b7b6bc
34 changed files with 3423 additions and 100 deletions

View File

@@ -20,17 +20,28 @@ import PackageDescription
let package = Package( let package = Package(
name: "FlatBuffers", name: "FlatBuffers",
platforms: [ platforms: [
.iOS(.v11), .iOS(.v12),
.macOS(.v10_14), .macOS(.v10_14),
], ],
products: [ products: [
.library( .library(
name: "FlatBuffers", name: "FlatBuffers",
targets: ["FlatBuffers"]), targets: ["FlatBuffers"]),
.library(
name: "FlexBuffers",
targets: ["FlexBuffers"]),
], ],
targets: [ targets: [
.target( .target(
name: "FlatBuffers", name: "FlatBuffers",
dependencies: ["Common"],
path: "swift/Sources/FlatBuffers"),
.target(
name: "FlexBuffers",
dependencies: ["Common"],
path: "swift/Sources/FlexBuffers"),
.target(
name: "Common",
dependencies: [], dependencies: [],
path: "swift/Sources"), path: "swift/Sources/Common"),
]) ])

View File

@@ -20,7 +20,7 @@ import PackageDescription
let package = Package( let package = Package(
name: "Greeter", name: "Greeter",
platforms: [ platforms: [
.iOS(.v11), .iOS(.v12),
.macOS(.v10_14), .macOS(.v10_14),
], ],
dependencies: [ dependencies: [

View File

@@ -23,5 +23,6 @@
--exclude **/*_generated.swift --exclude **/*_generated.swift
--exclude **/swift_code_*.swift --exclude **/swift_code_*.swift
--exclude **/*.grpc.swift --exclude **/*.grpc.swift
--exclude **/Build/tests/**
--header "/*\n * Copyright 2024 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */" --header "/*\n * Copyright 2024 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */"

View File

@@ -1,8 +1,21 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "FlexBuffers",
srcs = glob([
"Sources/FlexBuffers/**/*.swift",
"Sources/Common/*.swift",
]),
module_name = "FlexBuffers",
visibility = ["//visibility:public"],
)
swift_library( swift_library(
name = "swift", name = "swift",
srcs = glob(["Sources/FlatBuffers/*.swift"]), srcs = glob([
"Sources/FlatBuffers/*.swift",
"Sources/Common/*.swift",
]),
module_name = "FlatBuffers", module_name = "FlatBuffers",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )

View File

@@ -22,9 +22,10 @@ extension Int {
/// ///
/// This is used since the UnsafeMutableRawPointer will face issues when writing/reading /// This is used since the UnsafeMutableRawPointer will face issues when writing/reading
/// if the buffer alignment exceeds that actual size of the buffer /// if the buffer alignment exceeds that actual size of the buffer
var convertToPowerofTwo: Int { @inline(__always)
public var convertToPowerofTwo: Int {
guard self > 0 else { return 1 } guard self > 0 else { return 1 }
var n = UOffset(self) var n = UInt32(self)
#if arch(arm) || arch(i386) #if arch(arm) || arch(i386)
let max = UInt32(Int.max) let max = UInt32(Int.max)

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// A boolean to see if the system is littleEndian
public let isLitteEndian: Bool = {
let number: UInt32 = 0x12345678
return number == number.littleEndian
}()
/// Constant for the file id length
public let FileIdLength = 4
/// Protocol that All Scalars should conform to
///
/// Scalar is used to conform all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer.
public protocol Scalar: Equatable {
associatedtype NumericValue
var convertedEndian: NumericValue { get }
}
extension Scalar where Self: FixedWidthInteger {
/// Converts the value from BigEndian to LittleEndian
///
/// Converts values to little endian on machines that work with BigEndian, however this is NOT TESTED yet.
public var convertedEndian: NumericValue {
self as! Self.NumericValue
}
}
extension Double: Scalar {
public typealias NumericValue = UInt64
public var convertedEndian: UInt64 {
bitPattern.littleEndian
}
}
extension Float32: Scalar {
public typealias NumericValue = UInt32
public var convertedEndian: UInt32 {
bitPattern.littleEndian
}
}
extension Bool: Scalar {
public var convertedEndian: UInt8 {
self == true ? 1 : 0
}
public typealias NumericValue = UInt8
}
extension Int: Scalar {
public typealias NumericValue = Int
}
extension Int8: Scalar {
public typealias NumericValue = Int8
}
extension Int16: Scalar {
public typealias NumericValue = Int16
}
extension Int32: Scalar {
public typealias NumericValue = Int32
}
extension Int64: Scalar {
public typealias NumericValue = Int64
}
extension UInt: Scalar {
public typealias NumericValue = UInt
}
extension UInt8: Scalar {
public typealias NumericValue = UInt8
}
extension UInt16: Scalar {
public typealias NumericValue = UInt16
}
extension UInt32: Scalar {
public typealias NumericValue = UInt32
}
extension UInt64: Scalar {
public typealias NumericValue = UInt64
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Gets the padding for the current element
/// - Parameters:
/// - bufSize: Current size of the buffer + the offset of the object to be written
/// - elementSize: Element size
@inline(__always)
public func padding(
bufSize: UInt,
elementSize: UInt) -> UInt
{
((~bufSize) &+ 1) & (elementSize &- 1)
}

View File

@@ -293,7 +293,7 @@ public struct ByteBuffer {
} }
assert(index < _storage.capacity, "Write index is out of writing bound") assert(index < _storage.capacity, "Write index is out of writing bound")
assert(index >= 0, "Writer index should be above zero") assert(index >= 0, "Writer index should be above zero")
_ = withUnsafePointer(to: value) { ptr in withUnsafePointer(to: value) { ptr in
_storage.withUnsafeRawPointer { _storage.withUnsafeRawPointer {
memcpy( memcpy(
$0.advanced(by: index), $0.advanced(by: index),

View File

@@ -14,15 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
#if canImport(Common)
@_exported import Common
#endif
import Foundation import Foundation
/// A boolean to see if the system is littleEndian
let isLitteEndian: Bool = {
let number: UInt32 = 0x12345678
return number == number.littleEndian
}()
/// Constant for the file id length
let FileIdLength = 4
/// Type aliases /// Type aliases
public typealias Byte = UInt8 public typealias Byte = UInt8
public typealias UOffset = UInt32 public typealias UOffset = UInt32
@@ -35,80 +31,31 @@ public let FlatBufferMaxSize = UInt32
/// Protocol that All Scalars should conform to /// Protocol that All Scalars should conform to
/// ///
/// Scalar is used to conform all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer. /// Scalar is used to conform all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer.
public protocol Scalar: Equatable {
associatedtype NumericValue
var convertedEndian: NumericValue { get }
}
extension Scalar where Self: Verifiable {} extension Scalar where Self: FixedWidthInteger {}
extension Scalar where Self: FixedWidthInteger { extension Double: Verifiable {}
/// Converts the value from BigEndian to LittleEndian
///
/// Converts values to little endian on machines that work with BigEndian, however this is NOT TESTED yet.
public var convertedEndian: NumericValue {
self as! Self.NumericValue
}
}
extension Double: Scalar, Verifiable { extension Float32: Verifiable {}
public typealias NumericValue = UInt64
public var convertedEndian: UInt64 { extension Bool: Verifiable {}
bitPattern.littleEndian
}
}
extension Float32: Scalar, Verifiable { extension Int: Verifiable {}
public typealias NumericValue = UInt32
public var convertedEndian: UInt32 { extension Int8: Verifiable {}
bitPattern.littleEndian
}
}
extension Bool: Scalar, Verifiable { extension Int16: Verifiable {}
public var convertedEndian: UInt8 {
self == true ? 1 : 0
}
public typealias NumericValue = UInt8 extension Int32: Verifiable {}
}
extension Int: Scalar, Verifiable { extension Int64: Verifiable {}
public typealias NumericValue = Int
}
extension Int8: Scalar, Verifiable { extension UInt8: Verifiable {}
public typealias NumericValue = Int8
}
extension Int16: Scalar, Verifiable { extension UInt16: Verifiable {}
public typealias NumericValue = Int16
}
extension Int32: Scalar, Verifiable { extension UInt32: Verifiable {}
public typealias NumericValue = Int32
}
extension Int64: Scalar, Verifiable { extension UInt64: Verifiable {}
public typealias NumericValue = Int64
}
extension UInt8: Scalar, Verifiable {
public typealias NumericValue = UInt8
}
extension UInt16: Scalar, Verifiable {
public typealias NumericValue = UInt16
}
extension UInt32: Scalar, Verifiable {
public typealias NumericValue = UInt32
}
extension UInt64: Scalar, Verifiable {
public typealias NumericValue = UInt64
}
public func FlatBuffersVersion_25_2_10() {} public func FlatBuffersVersion_25_2_10() {}

View File

@@ -343,19 +343,6 @@ public struct FlatBufferBuilder {
} }
} }
/// Gets the padding for the current element
/// - Parameters:
/// - bufSize: Current size of the buffer + the offset of the object to be written
/// - elementSize: Element size
@inline(__always)
@usableFromInline
mutating internal func padding(
bufSize: UInt32,
elementSize: UInt32) -> UInt32
{
((~bufSize) &+ 1) & (elementSize &- 1)
}
/// Prealigns the buffer before writting a new object into the buffer /// Prealigns the buffer before writting a new object into the buffer
/// - Parameters: /// - Parameters:
/// - len:Length of the object /// - len:Length of the object
@@ -364,11 +351,9 @@ public struct FlatBufferBuilder {
@usableFromInline @usableFromInline
mutating internal func preAlign(len: Int, alignment: Int) { mutating internal func preAlign(len: Int, alignment: Int) {
minAlignment(size: alignment) minAlignment(size: alignment)
_bb.fill( _bb.fill(padding: numericCast(padding(
padding: Int( bufSize: numericCast(_bb.size) &+ numericCast(len),
padding( elementSize: numericCast(alignment))))
bufSize: _bb.size &+ UOffset(len),
elementSize: UOffset(alignment))))
} }
/// Prealigns the buffer before writting a new object into the buffer /// Prealigns the buffer before writting a new object into the buffer

View File

@@ -253,7 +253,7 @@ struct _InternalByteBuffer {
} }
assert(index < _storage.capacity, "Write index is out of writing bound") assert(index < _storage.capacity, "Write index is out of writing bound")
assert(index >= 0, "Writer index should be above zero") assert(index >= 0, "Writer index should be above zero")
_ = withUnsafePointer(to: value) { withUnsafePointer(to: value) {
memcpy( memcpy(
_storage.memory.advanced(by: index), _storage.memory.advanced(by: index),
$0, $0,

View File

@@ -0,0 +1,482 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// `ByteBuffer` is the interface that stores the data for a `Flatbuffers` object
/// it allows users to write and read data directly from memory thus the use of its
/// functions should be used
@frozen
public struct ByteBuffer {
/// Storage is a container that would hold the memory pointer to solve the issue of
/// deallocating the memory that was held by (memory: UnsafeMutableRawPointer)
@usableFromInline
final class Storage {
@usableFromInline
enum Blob {
#if !os(WASI)
case data(Data)
case bytes(ContiguousBytes)
#endif
case byteBuffer(_InternalByteBuffer)
case array([UInt8])
case pointer(UnsafeMutableRawPointer)
}
/// This storage doesn't own the memory, therefore, we won't deallocate on deinit.
private let isOwned: Bool
/// Retained blob of data that requires the storage to retain a pointer to.
@usableFromInline
var retainedBlob: Blob
/// Capacity of UInt8 the buffer can hold
var capacity: Int
@usableFromInline
init(count: Int) {
let memory = UnsafeMutableRawPointer.allocate(
byteCount: count,
alignment: MemoryLayout<UInt8>.alignment)
capacity = count
retainedBlob = .pointer(memory)
isOwned = true
}
@usableFromInline
init(blob: Blob, capacity count: Int) {
capacity = count
retainedBlob = blob
isOwned = false
}
deinit {
guard isOwned else { return }
switch retainedBlob {
case .pointer(let unsafeMutableRawPointer):
unsafeMutableRawPointer.deallocate()
default: break
}
}
@usableFromInline
func copy(from ptr: UnsafeRawPointer, count: Int) {
assert(
isOwned,
"copy should NOT be called on a buffer that is built by assumingMemoryBound")
withUnsafeRawPointer {
$0.copyMemory(from: ptr, byteCount: count)
}
}
@usableFromInline
func initialize(for size: Int) {
assert(
isOwned,
"initalize should NOT be called on a buffer that is built by assumingMemoryBound")
withUnsafeRawPointer {
memset($0, 0, size)
}
}
@discardableResult
@inline(__always)
func withUnsafeBytes<T>(
_ body: (UnsafeRawBufferPointer) throws
-> T) rethrows -> T
{
switch retainedBlob {
case .byteBuffer(let byteBuffer):
return try byteBuffer.withUnsafeBytes(body)
#if !os(WASI)
case .data(let data):
return try data.withUnsafeBytes(body)
case .bytes(let contiguousBytes):
return try contiguousBytes.withUnsafeBytes(body)
#endif
case .array(let array):
return try array.withUnsafeBytes(body)
case .pointer(let ptr):
return try body(UnsafeRawBufferPointer(start: ptr, count: capacity))
}
}
@discardableResult
@inline(__always)
func withUnsafeRawPointer<T>(
_ body: (UnsafeMutableRawPointer) throws
-> T) rethrows -> T
{
switch retainedBlob {
case .byteBuffer(let byteBuffer):
return try byteBuffer.withUnsafeRawPointer(body)
#if !os(WASI)
case .data(let data):
return try data
.withUnsafeBytes {
try body(UnsafeMutableRawPointer(mutating: $0.baseAddress!))
}
case .bytes(let contiguousBytes):
return try contiguousBytes
.withUnsafeBytes {
try body(UnsafeMutableRawPointer(mutating: $0.baseAddress!))
}
#endif
case .array(let array):
return try array
.withUnsafeBytes {
try body(UnsafeMutableRawPointer(mutating: $0.baseAddress!))
}
case .pointer(let ptr):
return try body(ptr)
}
}
@discardableResult
@inline(__always)
func readWithUnsafeRawPointer<T>(
position: Int,
_ body: (UnsafeRawPointer) throws -> T) rethrows -> T
{
switch retainedBlob {
case .byteBuffer(let byteBuffer):
return try byteBuffer.readWithUnsafeRawPointer(position: position, body)
#if !os(WASI)
case .data(let data):
return try data.withUnsafeBytes {
try body($0.baseAddress!.advanced(by: position))
}
case .bytes(let contiguousBytes):
return try contiguousBytes.withUnsafeBytes {
try body($0.baseAddress!.advanced(by: position))
}
#endif
case .array(let array):
return try array.withUnsafeBytes {
try body($0.baseAddress!.advanced(by: position))
}
case .pointer(let ptr):
return try body(ptr.advanced(by: position))
}
}
}
@usableFromInline var _storage: Storage
/// The size of the elements written to the buffer + their paddings
private var _readerIndex: Int = 0
// /// Reader is the position of the current Writer Index (capacity - size)
// var reader: Int { _storage.capacity &- _readerIndex }
/// Current size of the buffer
public var count: Int { _readerIndex }
/// Current capacity for the buffer including unused space
public var capacity: Int { _storage.capacity }
/// Constructor that creates a Flatbuffer object from an InternalByteBuffer
/// - Parameter
/// - bytes: Array of UInt8
@inline(__always)
init(byteBuffer: _InternalByteBuffer) {
_storage = Storage(
blob: .byteBuffer(byteBuffer),
capacity: byteBuffer.capacity)
_readerIndex = byteBuffer.writerIndex
}
/// Constructor that creates a Flatbuffer from unsafe memory region by copying
/// the underlying data to a new pointer
///
/// - Parameters:
/// - copyingMemoryBound: The unsafe memory region
/// - capacity: The size of the given memory region
@inline(__always)
public init(
copyingMemoryBound memory: UnsafeRawPointer,
capacity: Int)
{
_storage = Storage(count: capacity)
_storage.copy(from: memory, count: capacity)
_readerIndex = _storage.capacity
}
/// Constructor that creates a Flatbuffer object from a UInt8
/// - Parameter
/// - bytes: Array of UInt8
@inline(__always)
public init(bytes: [UInt8]) {
_storage = Storage(blob: .array(bytes), capacity: bytes.count)
_readerIndex = _storage.capacity
}
#if !os(WASI)
/// Constructor that creates a Flatbuffer from the Swift Data type object
/// - Parameter
/// - data: Swift data Object
@inline(__always)
public init(data: Data) {
_storage = Storage(blob: .data(data), capacity: data.count)
_readerIndex = _storage.capacity
}
/// Constructor that creates a Flatbuffer object from a ContiguousBytes
/// - Parameters:
/// - contiguousBytes: Binary stripe to use as the buffer
/// - count: amount of readable bytes
@inline(__always)
public init<Bytes: ContiguousBytes>(
contiguousBytes: Bytes,
count: Int)
{
_storage = Storage(blob: .bytes(contiguousBytes), capacity: count)
_readerIndex = _storage.capacity
}
#endif
/// Constructor that creates a Flatbuffer from unsafe memory region without copying
/// **NOTE** Needs a call to `memory.deallocate()` later on to free the memory
///
/// - Parameters:
/// - assumingMemoryBound: The unsafe memory region
/// - capacity: The size of the given memory region
@inline(__always)
public init(
assumingMemoryBound memory: UnsafeMutableRawPointer,
capacity: Int)
{
_storage = Storage(
blob: .pointer(memory),
capacity: capacity)
_readerIndex = _storage.capacity
}
/// Creates a copy of the existing flatbuffer, by copying it to a different memory.
/// - Parameters:
/// - memory: Current memory of the buffer
/// - count: count of bytes
/// - removeBytes: Removes a number of bytes from the current size
@inline(__always)
init(
blob: Storage.Blob,
count: Int,
removing removeBytes: Int)
{
_storage = Storage(blob: blob, capacity: count)
_readerIndex = removeBytes
}
/// Write stores an object into the buffer directly or indirectly.
///
/// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory
/// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end
/// - Parameters:
/// - value: Value that needs to be written to the buffer
/// - index: index to write to
/// - direct: Should take into consideration the capacity of the buffer
@inline(__always)
func write<T>(value: T, index: Int, direct: Bool = false) {
var index = index
if !direct {
index = _storage.capacity &- index
}
assert(index < _storage.capacity, "Write index is out of writing bound")
assert(index >= 0, "Writer index should be above zero")
withUnsafePointer(to: value) { ptr in
_storage.withUnsafeRawPointer {
memcpy(
$0.advanced(by: index),
ptr,
MemoryLayout<T>.size)
}
}
}
/// Reads an object from the buffer
/// - Parameters:
/// - def: Type of the object
/// - position: the index of the object in the buffer
@inline(__always)
public func read<T>(def: T.Type, position: Int) -> T {
_storage.readWithUnsafeRawPointer(position: position) {
$0.bindMemory(to: T.self, capacity: 1)
.pointee
}
}
@inline(__always)
public func readUInt64(offset: Int, byteWidth: UInt8) -> UInt64 {
readSizedScalar(
def: UInt64.self,
t1: UInt8.self,
t2: UInt16.self,
t3: UInt32.self,
t4: UInt64.self,
position: offset,
byteWidth: byteWidth)
}
@inline(__always)
public func readInt64(offset: Int, byteWidth: UInt8) -> Int64 {
readSizedScalar(
def: Int64.self,
t1: Int8.self,
t2: Int16.self,
t3: Int32.self,
t4: Int64.self,
position: offset,
byteWidth: byteWidth)
}
@inline(__always)
public func readDouble(offset: Int, byteWidth: UInt8) -> Double {
switch byteWidth {
case 4:
Double(read(def: Float32.self, position: offset))
default:
read(def: Double.self, position: offset)
}
}
@inline(__always)
func readSizedScalar<
T: BinaryInteger,
T1: BinaryInteger,
T2: BinaryInteger,
T3: BinaryInteger,
T4: BinaryInteger
>(
def: T.Type,
t1: T1.Type,
t2: T2.Type,
t3: T3.Type,
t4: T4.Type,
position: Int,
byteWidth: UInt8) -> T
{
switch byteWidth {
case 1:
numericCast(read(def: T1.self, position: position))
case 2:
numericCast(read(def: T2.self, position: position))
case 4:
numericCast(read(def: T3.self, position: position))
default:
numericCast(read(def: T4.self, position: position))
}
}
/// Reads a slice from the memory assuming a type of T
/// - Parameters:
/// - index: index of the object to be read from the buffer
/// - count: count of bytes in memory
@inline(__always)
public func readSlice<T>(
index: Int,
count: Int) -> [T]
{
assert(
index + count <= _storage.capacity,
"Reading out of bounds is illegal")
return _storage.readWithUnsafeRawPointer(position: index) {
let buf = UnsafeBufferPointer(
start: $0.bindMemory(to: T.self, capacity: count),
count: count)
return Array(buf)
}
}
@inline(__always)
public func readString(
at index: Int,
count: Int,
type: String.Encoding) -> String?
{
assert(
index + count <= _storage.capacity,
"Reading out of bounds is illegal")
return _storage.readWithUnsafeRawPointer(position: index) {
let buf = UnsafeBufferPointer(
start: $0.bindMemory(to: UInt8.self, capacity: count),
count: count)
return String(
bytes: buf,
encoding: type)
}
}
/// Reads a string from the buffer and encodes it to a swift string
/// - Parameters:
/// - index: index of the string in the buffer
/// - count: length of the string
@inline(__always)
public func readString(
at index: Int,
count: Int) -> String?
{
assert(
index + count <= _storage.capacity,
"Reading out of bounds is illegal")
return _storage.readWithUnsafeRawPointer(position: index) {
String(cString: $0.bindMemory(to: UInt8.self, capacity: count))
}
}
/// Provides a pointer towards the underlying primitive types
/// - Parameters:
/// - index: index of the object to be read from the buffer
/// - count: count of bytes in memory
@discardableResult
@inline(__always)
public func withUnsafePointerToSlice<T>(
index: Int,
count: Int,
body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T
{
assert(
index + count <= _storage.capacity,
"Reading out of bounds is illegal")
return try _storage.readWithUnsafeRawPointer(position: index) {
try body(UnsafeRawBufferPointer(start: $0, count: count))
}
}
@discardableResult
@inline(__always)
public func withUnsafeBytes<T>(
body: (UnsafeRawBufferPointer) throws
-> T) rethrows -> T
{
try _storage.withUnsafeBytes(body)
}
@discardableResult
@inline(__always)
func withUnsafeMutableRawPointer<T>(
body: (UnsafeMutableRawPointer) throws
-> T) rethrows -> T
{
try _storage.withUnsafeRawPointer(body)
}
@discardableResult
@inline(__always)
func readWithUnsafeRawPointer<T>(
position: Int,
_ body: (UnsafeRawPointer) throws -> T) rethrows -> T
{
try _storage.readWithUnsafeRawPointer(position: position, body)
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
public enum FlexBufferType: UInt64 {
case null = 0
/// Variable width signed integer: `Int8, Int16, Int32, Int64`
case int = 1
/// Variable width signed integer: `UInt8, UInt16, UInt32, UInt64`
case uint = 2
/// Variable width floating point: `Float32, Double64`
case float = 3
case key = 4
case string = 5
/// An Int, stored by offset rather than inline. Indirect types can keep the bitwidth of a
/// vector or map small when the inline value would have increased the bitwidth.
case indirectInt = 6
/// A UInt, stored by offset rather than inline. Indirect types can keep the bitwidth of a
/// vector or map small when the inline value would have increased the bitwidth.
case indirectUInt = 7
/// A Float, stored by offset rather than inline. Indirect types can keep the bitwidth of a
/// vector or map small when the inline value would have increased the bitwidth.
case indirectFloat = 8
case map = 9
/// Untyped
case vector = 10
/// Typed any sizes (stores no type table).
case vectorInt = 11
case vectorUInt = 12
case vectorFloat = 13
case vectorKey = 14
@available(
*,
deprecated,
message: "use FBT_VECTOR or FBT_VECTOR_KEY instead.")
case vectorString = 15
/// Typed tuples (no type table, no size field).
case vectorInt2 = 16
case vectorUInt2 = 17
case vectorFloat2 = 18
/// Typed triples (no type table, no size field).
case vectorInt3 = 19
case vectorUInt3 = 20
case vectorFloat3 = 21
/// Typed quad (no type table, no size field).
case vectorInt4 = 22
case vectorUInt4 = 23
case vectorFloat4 = 24
case blob = 25
case bool = 26
/// To Allow the same type of conversion of type to vector type
case vectorBool = 36
case max = 37
}
extension FlexBufferType: Comparable {
public static func < (lhs: FlexBufferType, rhs: FlexBufferType) -> Bool {
lhs.rawValue < rhs.rawValue
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
public struct FixedTypedVector: FlexBufferVector {
public let byteBuffer: ByteBuffer
public let offset: Int
public let type: FlexBufferType
public let count: Int
public var isEmpty: Bool { count == 0 }
let byteWidth: UInt8
@inline(__always)
init(
byteBuffer: ByteBuffer,
offset: Int,
byteWidth: UInt8,
type: FlexBufferType,
count: Int)
{
self.byteBuffer = byteBuffer
self.offset = offset
self.byteWidth = byteWidth
self.type = type
self.count = count
}
@inline(__always)
public subscript(index: Int) -> Reference? {
let elementOffset = offset &+ (numericCast(index) &* numericCast(byteWidth))
return Reference(
byteBuffer: byteBuffer,
offset: elementOffset,
parentWidth: byteWidth,
byteWidth: 1,
type: type)
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
protocol FlexBufferVector: Sized & FlexBufferContiguousBytes {
subscript(index: Int) -> Reference? { get }
}
extension FlexBufferVector {
public func jsonBuilder(json: inout String) {
json += "["
for i in 0..<count {
if let val = self[i]?.jsonString() {
let comma = i == count - 1 ? "" : ", "
json += "\(val)\(comma)"
}
}
json += "]"
}
}
public protocol FlexBufferContiguousBytes {
var byteBuffer: ByteBuffer { get }
var offset: Int { get }
var count: Int { get }
func withUnsafeRawBufferPointer<Result>(
_ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
}
extension FlexBufferContiguousBytes {
public func withUnsafeRawBufferPointer<Result>(
_ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
{
try byteBuffer.withUnsafePointerToSlice(
index: offset,
count: count,
body: body)
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public struct Map: Sized {
let byteBuffer: ByteBuffer
let offset: Int
let byteWidth: UInt8
public let keys: TypedVector
public let count: Int
public var values: Vector {
return Vector(byteBuffer: byteBuffer, offset: offset, byteWidth: byteWidth)
}
@inline(__always)
init(byteBuffer: ByteBuffer, offset: Int, byteWidth: UInt8) {
self.byteBuffer = byteBuffer
self.offset = offset
self.byteWidth = byteWidth
count = getCount(buffer: byteBuffer, offset: offset, byteWidth: byteWidth)
keys = TypedVector.mapKeys(
byteBuffer: byteBuffer,
offset: offset,
byteWidth: byteWidth)
}
@inline(__always)
public subscript(key: String) -> Reference? {
guard let position = binarySearch(vector: keys, target: key)
else { return nil }
return getReference(at: position)
}
}
extension Map {
public func jsonBuilder(json: inout String) {
json += "{"
for i in 0..<count {
if let key = keys[i]?.cString {
let comma = i == count - 1 ? "" : ", "
let value = values[i]?.jsonString() ?? StaticJSON.null
json += "\"\(key)\": \(value)\(comma)"
}
}
json += "}"
}
}

View File

@@ -0,0 +1,298 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
enum FlexBuffersErrors: Error {
case sizeOfBufferIsTooSmall
case typeCouldNotBeDetermined
}
@inline(__always)
public func getRoot(buffer: ByteBuffer) throws -> Reference? {
let end = buffer.count
if buffer.count < 3 {
throw FlexBuffersErrors.sizeOfBufferIsTooSmall
}
let byteWidth = buffer.read(def: UInt8.self, position: end &- 1)
let packedType = buffer.read(def: UInt8.self, position: end &- 2)
let offset = end &- 2 &- numericCast(byteWidth)
return Reference(
byteBuffer: buffer,
offset: offset,
parentWidth: byteWidth,
packedType: packedType)
}
@inline(__always)
public func getRootChecked(buffer: ByteBuffer) throws -> Reference? {
// TODO(mustiikhalil): implement verifier
return try getRoot(buffer: buffer)
}
public struct Reference {
private let byteBuffer: ByteBuffer
private let offset: Int
private let parentWidth: UInt8
private let byteWidth: UInt8
public let type: FlexBufferType
@inline(__always)
init?(
byteBuffer: ByteBuffer,
offset: Int,
parentWidth: UInt8,
packedType: UInt8)
{
guard let type = FlexBufferType(rawValue: UInt64(packedType >> 2)) else {
return nil
}
self.byteBuffer = byteBuffer
self.offset = offset
self.parentWidth = parentWidth
byteWidth = 1 << (packedType & 3)
self.type = type
}
@inline(__always)
init(
byteBuffer: ByteBuffer,
offset: Int,
parentWidth: UInt8,
byteWidth: UInt8,
type: FlexBufferType)
{
self.byteBuffer = byteBuffer
self.offset = offset
self.parentWidth = parentWidth
self.byteWidth = byteWidth
self.type = type
}
@inline(__always)
public var bool: Bool? {
return switch type {
case .bool: byteBuffer.readUInt64(offset: offset, byteWidth: byteWidth) != 0
default: nil
}
}
@inline(__always)
public var uint: UInt64? {
return switch type {
case .uint: byteBuffer.readUInt64(offset: offset, byteWidth: byteWidth)
case .indirectUInt: byteBuffer.readUInt64(
offset: indirect(),
byteWidth: byteWidth)
default: nil
}
}
@inline(__always)
public var int: Int64? {
return switch type {
case .int: byteBuffer.readInt64(offset: offset, byteWidth: byteWidth)
case .indirectInt: byteBuffer.readInt64(
offset: indirect(),
byteWidth: byteWidth)
default: nil
}
}
@inline(__always)
public var double: Double? {
return switch type {
case .float: byteBuffer.readDouble(offset: offset, byteWidth: byteWidth)
case .indirectFloat: byteBuffer.readDouble(
offset: indirect(),
byteWidth: byteWidth)
default: nil
}
}
@inline(__always)
public var map: Map? {
guard type == .map else { return nil }
return Map(
byteBuffer: byteBuffer,
offset: indirect(),
byteWidth: byteWidth)
}
@inline(__always)
public var vector: Vector? {
guard type == .vector || type == .map else { return nil }
return Vector(
byteBuffer: byteBuffer,
offset: indirect(),
byteWidth: byteWidth)
}
@inline(__always)
public var cString: String? {
guard type == .string || type == .key else { return nil }
let offset = indirect()
let count = getCount(
buffer: byteBuffer,
offset: offset,
byteWidth: byteWidth)
return byteBuffer.readString(
at: offset,
count: count)
}
@inline(__always)
public func blob<Result>(_ completion: (UnsafeRawBufferPointer) -> Result)
-> Result?
{
guard type == .blob || type == .string else { return nil }
let offset = indirect()
let count = getCount(
buffer: byteBuffer,
offset: offset,
byteWidth: byteWidth)
return byteBuffer.withUnsafePointerToSlice(
index: offset,
count: count,
body: completion)
}
@inline(__always)
public var typedVector: TypedVector? {
guard isTypedVectorType(type: type) else { return nil }
guard var type = toTypedVectorElementType(type: type) else { return nil }
if type == .string {
type = .key
}
return TypedVector(
byteBuffer: byteBuffer,
offset: indirect(),
byteWidth: byteWidth,
type: type)
}
@inline(__always)
public var fixedTypedVector: FixedTypedVector? {
guard isFixedTypedVectorType(type: type) else { return nil }
let t = toFixedTypedVectorElementType(type: type)
guard let type = t.type else { return nil }
return FixedTypedVector(
byteBuffer: byteBuffer,
offset: indirect(),
byteWidth: byteWidth,
type: type,
count: t.count)
}
@inline(__always)
public func string(encoding: String.Encoding = .utf8) -> String? {
guard type == .string else { return nil }
let offset = indirect()
let count = getCount(
buffer: byteBuffer,
offset: offset,
byteWidth: byteWidth)
return byteBuffer.readString(
at: offset,
count: count,
type: encoding)
}
@inline(__always)
public func asInt<T: FixedWidthInteger>() -> T? {
guard let v = int else {
return nil
}
return numericCast(v)
}
@inline(__always)
public func asUInt<T: FixedWidthInteger>() -> T? {
guard let v = uint else {
return nil
}
return numericCast(v)
}
@inline(__always)
public func withUnsafeRawPointer<Result>(
_ completion: (UnsafeRawPointer) throws
-> Result)
rethrows -> Result?
{
return try byteBuffer.readWithUnsafeRawPointer(
position: indirect(),
completion)
}
private func indirect() -> Int {
readIndirect(buffer: byteBuffer, offset: offset, parentWidth)
}
}
extension Reference {
public func jsonString() -> String {
var str = ""
jsonBuilder(json: &str)
return str
}
func jsonBuilder(json: inout String) {
switch type {
case .null:
json += StaticJSON.null
case .uint, .indirectUInt:
json += uint.valueOrNull
case .int, .indirectInt:
json += int.valueOrNull
case .float, .indirectFloat:
json += double.valueOrNull
case .string, .key:
json += "\"\(cString ?? StaticJSON.null)\""
case .map:
map?.jsonBuilder(json: &json)
case .bool:
json += bool.valueOrNull
case .blob:
if let p = blob({ String(data: Data($0), encoding: .utf8) })?
.valueOrNull
{
json += "\"\(p)\""
} else {
json += StaticJSON.null
}
default:
if type == .vector {
vector?.jsonBuilder(json: &json)
} else if isTypedVectorType(type: type) {
typedVector?.jsonBuilder(json: &json)
} else if isFixedTypedVectorType(type: type) {
fixedTypedVector?.jsonBuilder(json: &json)
} else {
json += StaticJSON.null
}
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
protocol Sized {
var byteBuffer: ByteBuffer { get }
var offset: Int { get }
var byteWidth: UInt8 { get }
var count: Int { get }
}
extension Sized {
@inline(__always)
func getReference(at index: Int) -> Reference? {
if index >= count { return nil }
let bWidth = Int(byteWidth)
let packedType = byteBuffer.read(
def: UInt8.self,
position: (offset &+ (count &* bWidth)) &+ index)
let offset = offset &+ (index &* bWidth)
return Reference(
byteBuffer: byteBuffer,
offset: offset,
parentWidth: byteWidth,
packedType: packedType)
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
public struct TypedVector: FlexBufferVector {
public let byteBuffer: ByteBuffer
public let offset: Int
public let type: FlexBufferType
public let count: Int
public var isEmpty: Bool { count == 0 }
let byteWidth: UInt8
@inline(__always)
init(
byteBuffer: ByteBuffer,
offset: Int,
byteWidth: UInt8,
type: FlexBufferType)
{
self.byteBuffer = byteBuffer
self.offset = offset
self.byteWidth = byteWidth
self.type = type
count = getCount(buffer: byteBuffer, offset: offset, byteWidth: byteWidth)
}
@inline(__always)
public subscript(index: Int) -> Reference? {
let elementOffset = offset &+ (numericCast(index) &* numericCast(byteWidth))
return Reference(
byteBuffer: byteBuffer,
offset: elementOffset,
parentWidth: byteWidth,
byteWidth: 1,
type: type)
}
@inline(__always)
static func mapKeys(
byteBuffer: ByteBuffer,
offset: Int,
byteWidth: UInt8) -> TypedVector
{
let prefixedFields = 3
let keysOffset = offset &- (numericCast(byteWidth) &* prefixedFields)
let indirectOffset = readIndirect(
buffer: byteBuffer,
offset: keysOffset,
byteWidth)
let childByteWidth = byteBuffer.readUInt64(
offset: keysOffset &+ numericCast(byteWidth),
byteWidth: byteWidth)
return TypedVector(
byteBuffer: byteBuffer,
offset: indirectOffset,
byteWidth: numericCast(childByteWidth),
type: .key)
}
}
extension TypedVector {
@inline(__always)
func compare(offset off: Int, target: String) -> Int {
let elementOffset = offset &+ (off &* numericCast(byteWidth))
let indirectoffset = readIndirect(
buffer: byteBuffer,
offset: elementOffset,
byteWidth)
return byteBuffer.readWithUnsafeRawPointer(
position: indirectoffset)
{ bufPointer in
target.withCString { strPointer in
Int(strcmp(bufPointer, strPointer))
}
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// MARK: - Vector
public struct Vector: FlexBufferVector {
public let byteBuffer: ByteBuffer
public let offset: Int
public let count: Int
public var isEmpty: Bool { count == 0 }
let byteWidth: UInt8
@inline(__always)
init(byteBuffer: ByteBuffer, offset: Int, byteWidth: UInt8) {
self.byteBuffer = byteBuffer
self.offset = offset
self.byteWidth = byteWidth
count = getCount(
buffer: byteBuffer,
offset: offset,
byteWidth: byteWidth)
}
@inline(__always)
public subscript(index: Int) -> Reference? {
return getReference(at: index)
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
@usableFromInline
enum BitWidth: UInt64, CaseIterable {
case w8 = 0, w16 = 1, w32 = 2, w64 = 3
}
extension BitWidth: Comparable {
@usableFromInline
static func < (lhs: BitWidth, rhs: BitWidth) -> Bool {
lhs.rawValue < rhs.rawValue
}
@inline(__always)
static func widthB(_ v: Int) -> BitWidth {
switch v {
case 1: return .w8
case 2: return .w16
case 4: return .w32
case 8: return .w64
default:
assert(false, "We shouldn't reach here")
return .w64
}
}
@inline(__always)
static func max(_ lhs: BitWidth, rhs: BitWidth) -> BitWidth {
if lhs.rawValue > rhs.rawValue { return lhs }
return rhs
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if canImport(Common)
@_exported import Common
#endif
import Foundation
extension UInt64 {
static let one: UInt64 = 1
}
extension UInt32 {
static let one: UInt32 = 1
}
public enum BuilderFlag: UInt8 {
case none = 0
case shareKeys = 1
case shareStrings = 2
case shareKeysAndStrings = 3
case shareKeyVectors = 4
case shareAll = 7
}
extension BuilderFlag: Comparable {
public static func < (lhs: BuilderFlag, rhs: BuilderFlag) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
enum StaticJSON {
static let null = "null"
}
extension Optional {
var valueOrNull: String {
if let value = self {
return "\(value)"
} else {
return StaticJSON.null
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
public struct Value: Equatable {
@usableFromInline
enum Union: Equatable {
case i(Int64)
case u(UInt64)
case f(Double)
}
var sloc: Union
let type: FlexBufferType
let bitWidth: BitWidth
@inline(__always)
private init() {
sloc = .i(0)
type = .null
bitWidth = .w8
}
@inline(__always)
init(bool: Bool) {
sloc = .u(bool ? 1 : 0)
type = .bool
bitWidth = .w8
}
@inline(__always)
init(v: UInt64, type: FlexBufferType, bitWidth: BitWidth) {
sloc = .u(v)
self.type = type
self.bitWidth = bitWidth
}
@inline(__always)
init(v: Int64, type: FlexBufferType, bitWidth: BitWidth) {
sloc = .i(v)
self.type = type
self.bitWidth = bitWidth
}
@inline(__always)
init(v: Double, type: FlexBufferType, bitWidth: BitWidth) {
sloc = .f(v)
self.type = type
self.bitWidth = bitWidth
}
@inline(__always)
init(sloc: Union, type: FlexBufferType, bitWidth: BitWidth) {
self.sloc = sloc
self.type = type
self.bitWidth = bitWidth
}
@usableFromInline
var i: Int64 {
switch sloc {
case .i(let v): v
default: 0
}
}
@usableFromInline
var u: UInt64 {
switch sloc {
case .u(let v): v
default: 0
}
}
@usableFromInline
var f: Double {
switch sloc {
case .f(let v): v
default: 0
}
}
static let `nil` = Value()
}
extension Value {
@usableFromInline
@inline(__always)
func elementWidth(size: Int, index: UInt64) -> BitWidth {
if isInline(type) {
return bitWidth
} else {
for byteWidth in stride(from: 1, to: MemoryLayout<UInt64>.size, by: 2) {
let _offsetLoc: UInt64 = numericCast(numericCast(size) &+ padding(
bufSize: numericCast(size),
elementSize: numericCast(byteWidth)))
let offsetLoc = _offsetLoc &+ (index &* numericCast(byteWidth))
let offset = offsetLoc &- u
let bitWidth = widthU(offset)
if (UInt32.one << bitWidth.rawValue) == byteWidth {
return bitWidth
}
}
return .w64
}
}
@inline(__always)
func storedPackedType(width: BitWidth = .w8) -> UInt8 {
packedType(bitWidth: storedWidth(width: width), type: type)
}
@inline(__always)
private func storedWidth(width: BitWidth) -> BitWidth {
if isInline(type) {
return max(bitWidth, width)
} else {
return bitWidth
}
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
@inline(__always)
internal func isInline(_ t: FlexBufferType) -> Bool {
return t <= .float || t == .bool
}
@inline(__always)
private func check(_ v: UInt64, width: UInt64) -> Bool {
(v & ~((.one << width) &- 1)) == 0
}
@inline(__always)
internal func widthI(_ v: Int64) -> BitWidth {
let u = UInt64(bitPattern: v) << 1
return widthU(v >= 0 ? u : ~u)
}
@inline(__always)
internal func widthF(_ v: Double) -> BitWidth {
Double(Float(v)) == v ? .w32 : .w64
}
@inline(__always)
internal func widthU(_ v: UInt64) -> BitWidth {
if check(v, width: 8) { return .w8 }
if check(v, width: 16) { return .w16 }
if check(v, width: 32) { return .w32 }
return .w64
}
@inline(__always)
internal func packedType(bitWidth: BitWidth, type: FlexBufferType) -> UInt8 {
numericCast(bitWidth.rawValue | (type.rawValue << 2))
}
@inline(__always)
func getScalarType<T>(type: T.Type) -> FlexBufferType where T: Scalar {
if T.self is (any BinaryFloatingPoint.Type) {
return .float
}
if T.self is Bool.Type {
return .bool
}
if T.self is (any UnsignedInteger.Type) {
return .uint
}
return .int
}
@inline(__always)
func toTypedVector(type: FlexBufferType, length: UInt64) -> FlexBufferType {
let type: UInt64 = switch length {
case 0: type.rawValue &- FlexBufferType.int.rawValue &+ FlexBufferType
.vectorInt.rawValue
case 2: type.rawValue &- FlexBufferType.int.rawValue &+ FlexBufferType
.vectorInt2.rawValue
case 3: type.rawValue &- FlexBufferType.int.rawValue &+ FlexBufferType
.vectorInt3.rawValue
case 4: type.rawValue &- FlexBufferType.int.rawValue &+ FlexBufferType
.vectorInt4.rawValue
default: 0
}
return FlexBufferType(rawValue: type) ?? .null
}
@inline(__always)
func isTypedVectorElementType(type: FlexBufferType) -> Bool {
return type >= .int && type <= .string || type == .bool
}
@inline(__always)
func isTypedVectorType(type: FlexBufferType) -> Bool {
return type >= .vectorInt && type <= .vectorString || type == .vectorBool
}
@inline(__always)
func toTypedVectorElementType(type: FlexBufferType) -> FlexBufferType? {
return FlexBufferType(
rawValue: type.rawValue &- FlexBufferType.vectorInt
.rawValue &+ FlexBufferType.int.rawValue)
}
@inline(__always)
func isFixedTypedVectorType(type: FlexBufferType) -> Bool {
return type >= .vectorInt2 && type <= .vectorFloat4
}
@inline(__always)
func toFixedTypedVectorElementType(type: FlexBufferType)
-> (type: FlexBufferType?, count: Int)
{
assert(isFixedTypedVectorType(type: type))
let fixedType: UInt64 = numericCast(
type.rawValue &- FlexBufferType.vectorInt2
.rawValue)
let len: Int = numericCast((fixedType / 3) + 2)
return (
FlexBufferType(rawValue: (fixedType % 3) + FlexBufferType.int.rawValue),
len)
}
// MARK: - Reader functions
@inline(__always)
func binarySearch(
vector: TypedVector,
target: String) -> Int?
{
var left = 0
var right = vector.count
while left <= right {
let mid = left &+ (right &- left) / 2
let comp = vector.compare(offset: mid, target: target)
if comp == 0 {
return mid
} else if comp < 0 {
left = mid &+ 1
} else {
right = mid &- 1
}
}
return nil
}
@inline(__always)
func readIndirect(buffer: ByteBuffer, offset: Int, _ byteWidth: UInt8) -> Int {
return offset &- numericCast(buffer.readUInt64(
offset: offset,
byteWidth: byteWidth))
}
@inline(__always)
func getCount(buffer: ByteBuffer, offset: Int, byteWidth: UInt8) -> Int {
Int(buffer.readUInt64(
offset: offset &- numericCast(byteWidth),
byteWidth: byteWidth))
}

View File

@@ -0,0 +1,884 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
private let twentyFourBytes: Int = 24
public typealias FlexBuffersWriterBuilder = (inout FlexBuffersWriter) -> Void
public struct FlexBuffersWriter {
var capacity: Int {
_bb.capacity
}
var writerIndex: Int {
_bb.writerIndex
}
private var finished = false
private var hasDuplicatedKeys = false
private var minBitWidth: BitWidth = .w8
private var _bb: _InternalByteBuffer
private var stack: [Value] = []
private var keyPool: [Int: UInt] = [:]
private var stringPool: [Int: UInt] = [:]
private var flags: BuilderFlag
public init(initialSize: Int = 1024, flags: BuilderFlag = .shareKeys) {
_bb = _InternalByteBuffer(initialSize: initialSize)
self.flags = flags
}
/// Returns the written bytes into the ``ByteBuffer``
///
/// Should only be used after ``finish(offset:addPrefix:)`` is called
public var sizedByteArray: [UInt8] {
assert(
finished == true,
"function finish() should be called before accessing data")
return _bb.underlyingBytes
}
public var sizedByteBuffer: ByteBuffer {
assert(
finished == true,
"function finish() should be called before accessing data")
return _bb.withUnsafeSlicedBytes {
ByteBuffer(copyingMemoryBound: $0.baseAddress!, capacity: $0.count)
}
}
public var byteBuffer: ByteBuffer {
assert(
finished == true,
"function finish() should be called before accessing data")
return ByteBuffer(byteBuffer: _bb)
}
/// Resets the internal state. Automatically called before building a new flexbuffer.
public mutating func reset() {
_bb.clear()
stack.removeAll(keepingCapacity: true)
finished = false
minBitWidth = .w8
keyPool.removeAll()
stringPool.removeAll()
}
// MARK: - Storing root
@inline(__always)
public mutating func finish() {
assert(stack.count == 1)
// Write root value.
var byteWidth = align(
width: stack[0].elementWidth(
size: writerIndex,
index: 0))
write(value: stack[0], byteWidth: byteWidth)
var storedType = stack[0].storedPackedType()
// Write root type.
_bb.writeBytes(&storedType, len: 1)
// Write root size. Normally determined by parent, but root has no parent :)
_bb.writeBytes(&byteWidth, len: 1)
finished = true
}
// MARK: - Vector
@discardableResult
@inline(__always)
public func startVector() -> Int {
stack.count
}
@discardableResult
@inline(__always)
public mutating func startVector(key k: String) -> Int {
add(key: k)
return stack.count
}
@discardableResult
@inline(__always)
public mutating func endVector(
start: Int,
typed: Bool = false,
fixed: Bool = false) -> UInt64
{
let vec = createVector(
start: start,
count: stack.count - start,
step: 1,
typed: typed,
fixed: fixed,
keys: nil)
stack = Array(stack[..<start])
stack.append(vec)
return vec.u
}
@discardableResult
@inline(__always)
public mutating func create<T>(vector: [T]) -> Int where T: Scalar {
create(vector: vector, fixed: false)
}
@discardableResult
@inline(__always)
public mutating func create<T>(vector: [T], key: borrowing String) -> Int
where T: Scalar
{
add(key: key)
return create(vector: vector, fixed: false)
}
@discardableResult
@inline(__always)
public mutating func createFixed<T>(vector: [T]) -> Int where T: Scalar {
assert(vector.count >= 2 && vector.count <= 4)
return create(vector: vector, fixed: true)
}
@discardableResult
@inline(__always)
public mutating func createFixed<T>(
vector: [T],
key: borrowing String) -> Int where T: Scalar
{
assert(vector.count >= 2 && vector.count <= 4)
add(key: key)
return create(vector: vector, fixed: true)
}
// MARK: - Map
@discardableResult
@inline(__always)
public func startMap() -> Int {
stack.count
}
@discardableResult
@inline(__always)
public mutating func startMap(key k: String) -> Int {
add(key: k)
return stack.count
}
@discardableResult
@inline(__always)
public mutating func endMap(start: Int) -> UInt64 {
let len = sortMapByKeys(start: start)
let keys = createVector(
start: start,
count: len,
step: 2,
typed: true,
fixed: false)
let vec = createVector(
start: start + 1,
count: len,
step: 2,
typed: false,
fixed: false,
keys: keys)
stack = Array(stack[..<start])
stack.append(vec)
return numericCast(vec.u)
}
// MARK: - Write Null
@inline(__always)
public mutating func addNil() {
stack.append(Value.nil)
}
@inline(__always)
public mutating func addNil(key: borrowing String) {
add(key: key)
addNil()
}
// MARK: - Write Bool
@inline(__always)
public mutating func add(bool: borrowing Bool) {
stack.append(Value(bool: bool))
}
@inline(__always)
public mutating func add(bool: borrowing Bool, key: borrowing String) {
add(key: key)
add(bool: bool)
}
// MARK: - Write UInt
@inline(__always)
public mutating func add(uint8 value: UInt8) {
add(uint64: numericCast(value))
}
@inline(__always)
public mutating func add(uint8 value: UInt8, key: borrowing String) {
add(key: key)
add(uint64: numericCast(value))
}
@inline(__always)
public mutating func add(uint16 value: UInt16) {
add(uint64: numericCast(value))
}
@inline(__always)
public mutating func add(uint16 value: UInt16, key: borrowing String) {
add(key: key)
add(uint64: numericCast(value))
}
@inline(__always)
public mutating func add(uint32 value: UInt32) {
add(uint64: numericCast(value))
}
@inline(__always)
public mutating func add(uint32 value: UInt32, key: borrowing String) {
add(key: key)
add(uint64: numericCast(value))
}
@inline(__always)
public mutating func add(uint value: UInt) {
add(uint64: numericCast(value))
}
@inline(__always)
public mutating func add(uint value: UInt, key: borrowing String) {
add(key: key)
add(uint64: numericCast(value))
}
@inline(__always)
public mutating func add(uint64 value: borrowing UInt64) {
stack.append(Value(v: value, type: .uint, bitWidth: widthU(value)))
}
@inline(__always)
public mutating func add(
uint64 value: borrowing UInt64,
key: borrowing String)
{
add(key: key)
add(uint64: value)
}
@inline(__always)
public mutating func indirect(uint64 val: borrowing UInt64) {
pushIndirect(value: val, type: .indirectUInt, bitWidth: widthU(val))
}
@inline(__always)
public mutating func indirect(
uint64 val: borrowing UInt64,
key: borrowing String)
{
add(key: key)
indirect(uint64: val)
}
@inline(__always)
public mutating func indirect(
uint val: borrowing UInt,
key: borrowing String)
{
add(key: key)
indirect(uint64: numericCast(val))
}
// MARK: - Write Int
@inline(__always)
public mutating func add(int8 value: Int8) {
add(int64: numericCast(value))
}
@inline(__always)
public mutating func add(int8 value: Int8, key: borrowing String) {
add(key: key)
add(int64: numericCast(value))
}
@inline(__always)
public mutating func add(int16 value: Int16) {
add(int64: numericCast(value))
}
@inline(__always)
public mutating func add(int16 value: Int16, key: borrowing String) {
add(key: key)
add(int64: numericCast(value))
}
@inline(__always)
public mutating func add(int32 value: Int32) {
add(int64: numericCast(value))
}
@inline(__always)
public mutating func add(int32 value: Int32, key: borrowing String) {
add(key: key)
add(int64: numericCast(value))
}
@inline(__always)
public mutating func add(int value: Int) {
add(int64: numericCast(value))
}
@inline(__always)
public mutating func add(int value: Int, key: borrowing String) {
add(key: key)
add(int64: numericCast(value))
}
@inline(__always)
public mutating func add(int64 value: borrowing Int64) {
stack.append(Value(v: value, type: .int, bitWidth: widthI(value)))
}
@inline(__always)
public mutating func add(
int64 value: borrowing Int64,
key: borrowing String)
{
add(key: key)
add(int64: value)
}
@inline(__always)
public mutating func indirect(int64 val: borrowing Int64) {
pushIndirect(value: val, type: .indirectInt, bitWidth: widthI(val))
}
@inline(__always)
public mutating func indirect(
int64 val: borrowing Int64,
key: borrowing String)
{
add(key: key)
indirect(int64: val)
}
@inline(__always)
public mutating func indirect(
int val: borrowing Int,
key: borrowing String)
{
add(key: key)
indirect(int64: numericCast(val))
}
// MARK: - Write Floats
@inline(__always)
public mutating func add(float32 value: borrowing Float32) {
stack.append(Value(v: Double(value), type: .float, bitWidth: .w32))
}
@inline(__always)
public mutating func add(
float32 value: borrowing Float32,
key: borrowing String)
{
add(key: key)
add(float32: value)
}
@inline(__always)
public mutating func indirect(float32 val: borrowing Float32) {
pushIndirect(value: val, type: .indirectFloat, bitWidth: .w32)
}
@inline(__always)
public mutating func indirect(
float32 val: borrowing Float32,
key: borrowing String)
{
add(key: key)
indirect(float32: val)
}
@inline(__always)
public mutating func add(double value: borrowing Double) {
stack.append(Value(v: value, type: .float, bitWidth: widthF(value)))
}
@inline(__always)
public mutating func add(
double value: borrowing Double,
key: borrowing String)
{
add(key: key)
add(double: value)
}
@inline(__always)
public mutating func indirect(double val: borrowing Double) {
pushIndirect(value: val, type: .indirectFloat, bitWidth: widthF(val))
}
@inline(__always)
public mutating func indirect(
double val: borrowing Double,
key: borrowing String)
{
add(key: key)
indirect(double: val)
}
// MARK: - Writing strings
@inline(__always)
public mutating func add(string: borrowing String, key: borrowing String) {
add(key: key)
write(str: string, len: string.count)
}
@inline(__always)
public mutating func add(string: borrowing String) {
write(str: string, len: string.count)
}
// MARK: - Writing Blobs
@discardableResult
@inline(__always)
public mutating func add<T>(
blob: borrowing T,
length l: Int) -> UInt where T: ContiguousBytes
{
storeBlob(blob, len: l, type: .blob)
}
@discardableResult
@inline(__always)
public mutating func add<T>(
blob: borrowing T,
key: borrowing String,
length l: Int) -> UInt where T: ContiguousBytes
{
add(key: key)
return storeBlob(blob, len: l, type: .blob)
}
// MARK: - Reuse Values
@inline(__always)
public func lastValue() -> Value? {
return stack.last
}
@inline(__always)
public mutating func reuse(value: Value) {
stack.append(value)
}
@inline(__always)
public mutating func reuse(value: Value, key: borrowing String) {
add(key: key)
reuse(value: value)
}
// MARK: - Private -
// MARK: Writing to buffer
@inline(__always)
private mutating func write(value: Value, byteWidth: Int) {
switch value.type {
case .null, .int: write(value: value.i, byteWidth: byteWidth)
case .bool, .uint: write(value: value.u, byteWidth: byteWidth)
case .float: write(double: value.f, byteWidth: byteWidth)
default:
write(offset: value.u, byteWidth: byteWidth)
}
}
@inline(__always)
private mutating func pushIndirect<T>(
value: T,
type: FlexBufferType,
bitWidth: BitWidth)
{
let byteWidth = align(width: bitWidth)
let iloc = writerIndex
_bb.ensureSpace(size: byteWidth)
_bb.write(value, len: byteWidth)
stack.append(
Value(
sloc: .u(numericCast(iloc)),
type: type,
bitWidth: bitWidth))
}
// MARK: Internal Writing Strings
/// Adds a string to the buffer using swift.utf8 object
/// - Parameter str: String that will be added to the buffer
/// - Parameter len: length of the string
@discardableResult
@inline(__always)
private mutating func write(str: borrowing String, len: Int) -> UInt {
let resetTo = writerIndex
var sloc = str.withCString {
storeBlob(pointer: $0, len: len, trailing: 1, type: .string)
}
if flags >= .shareKeysAndStrings {
let loc = stringPool[str.hashValue]
if let loc {
_bb.resetWriter(to: resetTo)
sloc = loc
assert(
stack.count > 0,
"Attempting to override the location, but stack is empty")
stack[stack.count - 1].sloc = .u(numericCast(sloc))
} else {
stringPool[str.hashValue] = sloc
}
}
return sloc
}
// MARK: Write Keys
@discardableResult
@inline(__always)
private mutating func add(key: borrowing String) -> UInt {
add(key: key, len: key.count)
}
@discardableResult
@inline(__always)
private mutating func add(key: borrowing String, len: Int) -> UInt {
_bb.ensureSpace(size: len)
var sloc: UInt = numericCast(writerIndex)
key.withCString {
_bb.writeBytes($0, len: len + 1)
}
if flags > .shareKeys {
let loc = keyPool[key.hashValue]
if let loc {
_bb.resetWriter(to: Int(sloc))
sloc = loc
} else {
keyPool[key.hashValue] = sloc
}
}
stack.append(Value(sloc: .u(numericCast(sloc)), type: .key, bitWidth: .w8))
return sloc
}
// MARK: - Storing Blobs
@inline(__always)
private mutating func storeBlob<T>(
_ bytes: T,
len: Int,
type: FlexBufferType) -> UInt where T: ContiguousBytes
{
return bytes.withUnsafeBytes {
storeBlob(pointer: $0.baseAddress!, len: len, type: type)
}
}
@discardableResult
@usableFromInline
@inline(__always)
mutating func storeBlob(
pointer: borrowing UnsafeRawPointer,
len: Int,
trailing: Int = 0,
type: FlexBufferType) -> UInt
{
_bb.ensureSpace(size: len &+ trailing)
let bitWidth = widthU(numericCast(len))
let bytes = align(width: bitWidth)
var len = len
_bb.writeBytes(&len, len: bytes)
let sloc = writerIndex
_bb.writeBytes(pointer, len: len &+ trailing)
stack.append(
Value(
sloc: .u(numericCast(sloc)),
type: type,
bitWidth: bitWidth))
return numericCast(sloc)
}
// MARK: Write Vectors
@discardableResult
@inline(__always)
private mutating func create<T>(vector: [T], fixed: Bool) -> Int
where T: Scalar
{
let length: UInt64 = numericCast(vector.count)
let vectorType = getScalarType(type: T.self)
let byteWidth = MemoryLayout<T>.size
let bitWidth = BitWidth.widthB(byteWidth)
_bb.ensureSpace(size: vector.count &* Int(bitWidth.rawValue))
assert(widthU(length) <= bitWidth)
align(width: bitWidth)
if !fixed {
write(value: length, byteWidth: byteWidth)
}
let vloc = _bb.writerIndex
for i in stride(from: 0, to: vector.count, by: 1) {
write(value: vector[i], byteWidth: byteWidth)
}
stack.append(
Value(
sloc: .u(numericCast(vloc)),
type: toTypedVector(type: vectorType, length: fixed ? length : 0),
bitWidth: bitWidth))
return vloc
}
@inline(__always)
private mutating func createVector(
start: Int,
count: Int,
step: Int,
typed: Bool,
fixed: Bool,
keys: Value? = nil) -> Value
{
assert(
!fixed || typed,
"Typed false and fixed true is a combination not supported currently")
var bitWidth = BitWidth.max(minBitWidth, rhs: widthU(numericCast(count)))
var prefixElements = 1
if keys != nil {
/// If this vector is part of a map, we will pre-fix an offset to the keys
/// to this vector.
bitWidth = max(bitWidth, keys!.elementWidth(size: writerIndex, index: 0))
prefixElements += 2
}
var vectorType: FlexBufferType = .key
for i in stride(from: start, to: stack.count, by: step) {
let elemWidth = stack[i].elementWidth(
size: _bb.writerIndex,
index: numericCast(i &- start &+ prefixElements))
bitWidth = BitWidth.max(bitWidth, rhs: elemWidth)
guard typed else { continue }
if i == start {
vectorType = stack[i].type
} else {
assert(
vectorType == stack[i].type,
"""
If you get this assert you are writing a typed vector
with elements that are not all the same type
""")
}
}
assert(
!typed || isTypedVectorElementType(type: vectorType),
"""
If you get this assert, your typed types are not one of:
Int / UInt / Float / Key.
""")
let byteWidth = align(width: bitWidth)
let currentSize: Int = count &* step &* byteWidth
let requiredSize: Int = if !typed {
// We ensure that we have enough space
// for loop two write operations &
// 24 bytes for when its not fixed,
// and keys isn't null. As an extra safe
// guard
(currentSize &* 2) &+ twentyFourBytes
} else {
currentSize
}
_bb.ensureSpace(
size: requiredSize)
if keys != nil {
write(offset: keys!.u, byteWidth: byteWidth)
write(value: UInt64.one << keys!.bitWidth.rawValue, byteWidth: byteWidth)
}
if !fixed {
write(value: count, byteWidth: byteWidth)
}
let vloc = _bb.writerIndex
for i in stride(from: start, to: stack.count, by: step) {
write(value: stack[i], byteWidth: byteWidth)
}
if !typed {
for i in stride(from: start, to: stack.count, by: step) {
_bb.write(stack[i].storedPackedType(width: bitWidth), len: 1)
}
}
let type: FlexBufferType =
if keys != nil {
.map
} else if typed {
toTypedVector(type: vectorType, length: numericCast(fixed ? count : 0))
} else {
.vector
}
return Value(sloc: .u(numericCast(vloc)), type: type, bitWidth: bitWidth)
}
// MARK: Write Scalar functions
@inline(__always)
private mutating func write(offset: UInt64, byteWidth: Int) {
let offset: UInt64 = numericCast(writerIndex) &- offset
assert(byteWidth == 8 || offset < UInt64.one << (byteWidth * 8))
withUnsafePointer(to: offset) {
_bb.writeBytes($0, len: byteWidth)
}
}
@inline(__always)
private mutating func write<T>(value: T, byteWidth: Int) where T: Scalar {
withUnsafePointer(to: value) {
_bb.writeBytes($0, len: byteWidth)
}
}
@inline(__always)
private mutating func write(double value: Double, byteWidth: Int) {
switch byteWidth {
case 8: write(value: value, byteWidth: byteWidth)
case 4: write(value: Float(value), byteWidth: byteWidth)
default: assert(false, "Should never reach here")
}
}
// MARK: Misc functions
@discardableResult
@inline(__always)
private mutating func align(width: BitWidth) -> Int {
let bytes: Int = numericCast(UInt32.one << width.rawValue)
_bb.addPadding(bytes: bytes)
return bytes
}
@inline(__always)
private mutating func sortMapByKeys(start: Int) -> Int {
let len = mapElementCount(start: start)
for index in stride(from: start, to: stack.count, by: 2) {
assert(stack[index].type == .key)
}
struct TwoValue: Equatable {
let key, value: Value
}
stack[start...].withUnsafeMutableBytes { buffer in
var ptr = buffer.assumingMemoryBound(to: TwoValue.self)
ptr.sort { a, b in
let aMem = _bb.memory.advanced(by: numericCast(a.key.u))
.assumingMemoryBound(to: CChar.self)
let bMem = _bb.memory.advanced(by: numericCast(b.key.u))
.assumingMemoryBound(to: CChar.self)
let comp = strcmp(aMem, bMem)
if (comp == 0) && a != b { hasDuplicatedKeys = true }
return comp < 0
}
}
return len
}
@inline(__always)
private func mapElementCount(start: Int) -> Int {
var len = stack.count - start
assert((len & 1) == 0)
len /= 2
return len
}
}
// MARK: - Vectors helper functions
extension FlexBuffersWriter {
@discardableResult
@inline(__always)
public mutating func vector(
key: String,
_ closure: @escaping FlexBuffersWriterBuilder) -> UInt64
{
let start = startVector(key: key)
closure(&self)
return endVector(start: start)
}
@discardableResult
@inline(__always)
public mutating func vector(_ closure: @escaping FlexBuffersWriterBuilder)
-> UInt64
{
let start = startVector()
closure(&self)
return endVector(start: start)
}
}
// MARK: - Maps helper functions
extension FlexBuffersWriter {
@discardableResult
@inline(__always)
public mutating func map(
key: String,
_ closure: @escaping FlexBuffersWriterBuilder) -> UInt64
{
let start = startMap(key: key)
closure(&self)
return endMap(start: start)
}
@discardableResult
@inline(__always)
public mutating func map(_ closure: @escaping FlexBuffersWriterBuilder)
-> UInt64
{
let start = startMap()
closure(&self)
return endMap(start: start)
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// `ByteBuffer` is the interface that stores the data for a `Flatbuffers` object
/// it allows users to write and read data directly from memory thus the use of its
/// functions should be used
@usableFromInline
struct _InternalByteBuffer {
/// Storage is a container that would hold the memory pointer to solve the issue of
/// deallocating the memory that was held by (memory: UnsafeMutableRawPointer)
@usableFromInline
final class Storage {
// This storage doesn't own the memory, therefore, we won't deallocate on deinit.
private let unowned: Bool
/// pointer to the start of the buffer object in memory
var memory: UnsafeMutableRawPointer
/// Capacity of UInt8 the buffer can hold
var capacity: Int
@usableFromInline
init(count: Int, alignment: Int) {
memory = UnsafeMutableRawPointer.allocate(
byteCount: count,
alignment: alignment)
capacity = count
unowned = false
}
@usableFromInline
init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) {
self.memory = memory
self.capacity = capacity
self.unowned = unowned
}
deinit {
if !unowned {
memory.deallocate()
}
}
@usableFromInline
func copy(from ptr: UnsafeRawPointer, count: Int) {
assert(
!unowned,
"copy should NOT be called on a buffer that is built by assumingMemoryBound")
memory.copyMemory(from: ptr, byteCount: count)
}
@usableFromInline
func initialize(for size: Int) {
assert(
!unowned,
"initalize should NOT be called on a buffer that is built by assumingMemoryBound")
memset(memory, 0, size)
}
/// Reallocates the buffer incase the object to be written doesnt fit in the current buffer
/// - Parameter size: Size of the current object
@usableFromInline
func reallocate(_ size: Int, writerSize: Int, alignment: Int) {
while capacity <= writerSize &+ size {
capacity = capacity << 1
}
/// solution take from Apple-NIO
capacity = capacity.convertToPowerofTwo
let newData = UnsafeMutableRawPointer.allocate(
byteCount: capacity,
alignment: alignment)
memset(newData, 0, capacity &- writerSize)
memcpy(
newData,
memory,
writerSize)
memory.deallocate()
memory = newData
}
}
@usableFromInline var _storage: Storage
/// The size of the elements written to the buffer + their paddings
var writerIndex: Int = 0
/// Alignment of the current memory being written to the buffer
private var alignment = 1
/// Public Pointer to the buffer object in memory. This should NOT be modified for any reason
public var memory: UnsafeMutableRawPointer { _storage.memory }
/// Current capacity for the buffer
public var capacity: Int { _storage.capacity }
/// Returns the written bytes into the ``ByteBuffer``
public var underlyingBytes: [UInt8] {
let start = memory.bindMemory(to: UInt8.self, capacity: writerIndex)
let ptr = UnsafeBufferPointer<UInt8>(start: start, count: writerIndex)
return Array(ptr)
}
/// Constructor that creates a Flatbuffer instance with a size
/// - Parameter:
/// - size: Length of the buffer
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
init(initialSize size: Int) {
let size = size.convertToPowerofTwo
_storage = Storage(count: size, alignment: alignment)
_storage.initialize(for: size)
}
/// Clears the current instance of the buffer, replacing it with new memory
@inline(__always)
mutating public func clear() {
writerIndex = 0
alignment = 1
_storage.initialize(for: _storage.capacity)
}
@inline(__always)
mutating public func resetWriter(to writer: Int) {
writerIndex = writer
}
/// Makes sure that buffer has enouch space for each of the objects that will be written into it
/// - Parameter size: size of object
@inline(__always)
mutating func ensureSpace(size: Int) {
guard size &+ writerIndex > _storage.capacity else { return }
_storage.reallocate(size, writerSize: writerIndex, alignment: alignment)
}
@inline(__always)
mutating func addPadding(bytes: Int) {
writerIndex = writerIndex &+ numericCast(padding(
bufSize: numericCast(writerIndex),
elementSize: numericCast(bytes)))
ensureSpace(size: writerIndex)
}
mutating func writeBytes(_ ptr: UnsafeRawPointer, len: Int) {
memcpy(
_storage.memory.advanced(by: writerIndex),
ptr,
len)
writerIndex = writerIndex &+ len
}
mutating func write<T>(_ v: T, len: Int) {
withUnsafePointer(to: v) {
memcpy(
_storage.memory.advanced(by: writerIndex),
$0,
len)
writerIndex = writerIndex &+ len
}
}
@discardableResult
@inline(__always)
func withUnsafeBytes<T>(
_ body: (UnsafeRawBufferPointer) throws
-> T) rethrows -> T
{
try body(UnsafeRawBufferPointer(
start: _storage.memory,
count: capacity))
}
@discardableResult
@inline(__always)
func withUnsafeSlicedBytes<T>(
_ body: (UnsafeRawBufferPointer) throws
-> T) rethrows -> T
{
try body(UnsafeRawBufferPointer(
start: _storage.memory,
count: writerIndex))
}
@discardableResult
@inline(__always)
func withUnsafeRawPointer<T>(
_ body: (UnsafeMutableRawPointer) throws
-> T) rethrows -> T
{
try body(_storage.memory)
}
@discardableResult
@inline(__always)
func readWithUnsafeRawPointer<T>(
position: Int,
_ body: (UnsafeRawPointer) throws -> T) rethrows -> T
{
try body(_storage.memory.advanced(by: position))
}
}
extension _InternalByteBuffer: CustomDebugStringConvertible {
public var debugDescription: String {
"""
buffer located at: \(_storage.memory), with capacity of \(_storage.capacity)
{ writerIndex: \(writerIndex) }
"""
}
}

View File

@@ -20,7 +20,7 @@ import PackageDescription
let package = Package( let package = Package(
name: "FlatBuffers.Test.Swift", name: "FlatBuffers.Test.Swift",
platforms: [ platforms: [
.iOS(.v11), .iOS(.v12),
.macOS(.v10_14), .macOS(.v10_14),
], ],
dependencies: [ dependencies: [
@@ -29,7 +29,9 @@ let package = Package(
// Prevent the build system from pulling 2.29.1 to prevent Swift 5.8 build breaks. // Prevent the build system from pulling 2.29.1 to prevent Swift 5.8 build breaks.
// The patch update introduced code that uses "switch expression syntax" that wasn't valid until Swift 5.9 [1]. // The patch update introduced code that uses "switch expression syntax" that wasn't valid until Swift 5.9 [1].
// [1] https://github.com/swiftlang/swift-evolution/blob/main/proposals/0380-if-switch-expressions.md // [1] https://github.com/swiftlang/swift-evolution/blob/main/proposals/0380-if-switch-expressions.md
.package(url: "https://github.com/apple/swift-nio-ssl.git", exact: "2.29.0"), .package(
url: "https://github.com/apple/swift-nio-ssl.git",
exact: "2.29.0"),
], ],
targets: [ targets: [
.executableTarget( .executableTarget(
@@ -43,4 +45,9 @@ let package = Package(
.product(name: "FlatBuffers", package: "flatbuffers"), .product(name: "FlatBuffers", package: "flatbuffers"),
.product(name: "GRPC", package: "grpc-swift"), .product(name: "GRPC", package: "grpc-swift"),
]), ]),
.testTarget(
name: "FlexBuffers.Test.SwiftTests",
dependencies: [
.product(name: "FlexBuffers", package: "flatbuffers"),
]),
]) ])

View File

@@ -49,7 +49,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase {
XCTAssertEqual(bytes.sizedByteArray, [48, 0, 0, 0, 77, 79, 78, 83, 0, 0, 0, 0, 36, 0, 72, 0, 40, 0, 0, 0, 38, 0, 32, 0, 0, 0, 28, 0, 0, 0, 27, 0, 20, 0, 16, 0, 12, 0, 4, 0, 0, 0, 0, 0, 0, 0, 11, 0, 36, 0, 0, 0, 164, 0, 0, 0, 0, 0, 0, 1, 60, 0, 0, 0, 68, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 120, 0, 0, 0, 0, 0, 80, 0, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 64, 2, 0, 5, 0, 6, 0, 0, 0, 2, 0, 0, 0, 64, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 30, 0, 40, 0, 10, 0, 20, 0, 152, 255, 255, 255, 4, 0, 0, 0, 4, 0, 0, 0, 70, 114, 101, 100, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 50, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 0, 0, 0, 9, 0, 0, 0, 77, 121, 77, 111, 110, 115, 116, 101, 114, 0, 0, 0, 3, 0, 0, 0, 20, 0, 0, 0, 36, 0, 0, 0, 4, 0, 0, 0, 240, 255, 255, 255, 32, 0, 0, 0, 248, 255, 255, 255, 36, 0, 0, 0, 12, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 12, 0, 0, 0, 28, 0, 0, 0, 5, 0, 0, 0, 87, 105, 108, 109, 97, 0, 0, 0, 6, 0, 0, 0, 66, 97, 114, 110, 101, 121, 0, 0, 5, 0, 0, 0, 70, 114, 111, 100, 111, 0, 0, 0]) XCTAssertEqual(bytes.sizedByteArray, [48, 0, 0, 0, 77, 79, 78, 83, 0, 0, 0, 0, 36, 0, 72, 0, 40, 0, 0, 0, 38, 0, 32, 0, 0, 0, 28, 0, 0, 0, 27, 0, 20, 0, 16, 0, 12, 0, 4, 0, 0, 0, 0, 0, 0, 0, 11, 0, 36, 0, 0, 0, 164, 0, 0, 0, 0, 0, 0, 1, 60, 0, 0, 0, 68, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 120, 0, 0, 0, 0, 0, 80, 0, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 64, 2, 0, 5, 0, 6, 0, 0, 0, 2, 0, 0, 0, 64, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 30, 0, 40, 0, 10, 0, 20, 0, 152, 255, 255, 255, 4, 0, 0, 0, 4, 0, 0, 0, 70, 114, 101, 100, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 50, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 0, 0, 0, 9, 0, 0, 0, 77, 121, 77, 111, 110, 115, 116, 101, 114, 0, 0, 0, 3, 0, 0, 0, 20, 0, 0, 0, 36, 0, 0, 0, 4, 0, 0, 0, 240, 255, 255, 255, 32, 0, 0, 0, 248, 255, 255, 255, 36, 0, 0, 0, 12, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 12, 0, 0, 0, 28, 0, 0, 0, 5, 0, 0, 0, 87, 105, 108, 109, 97, 0, 0, 0, 6, 0, 0, 0, 66, 97, 114, 110, 101, 121, 0, 0, 5, 0, 0, 0, 70, 114, 111, 100, 111, 0, 0, 0])
// swiftformat:enable all // swiftformat:enable all
var buffer = bytes.buffer var buffer = bytes.buffer
print(buffer)
let monster: MyGame_Example_Monster = getRoot(byteBuffer: &buffer) let monster: MyGame_Example_Monster = getRoot(byteBuffer: &buffer)
readMonster(monster: monster) readMonster(monster: monster)
mutateMonster(fb: bytes.buffer) mutateMonster(fb: bytes.buffer)
@@ -318,7 +318,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase {
func mutateMonster(fb: ByteBuffer) { func mutateMonster(fb: ByteBuffer) {
var fb = fb var fb = fb
print(fb)
let monster: Monster = getRoot(byteBuffer: &fb) let monster: Monster = getRoot(byteBuffer: &fb)
XCTAssertFalse(monster.mutate(mana: 10)) XCTAssertFalse(monster.mutate(mana: 10))
XCTAssertEqual(monster.testarrayoftables(at: 0)?.name, "Barney") XCTAssertEqual(monster.testarrayoftables(at: 0)?.name, "Barney")

View File

@@ -15,6 +15,7 @@
*/ */
import XCTest import XCTest
@testable import Common
@testable import FlatBuffers @testable import FlatBuffers
final class FlatBuffersTests: XCTestCase { final class FlatBuffersTests: XCTestCase {

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Common
import FlexBuffers
import XCTest
final class FlexBuffersJSONTests: XCTestCase {
func testEncodingJSON() throws {
let buf: ByteBuffer = createProperBuffer().sizedByteBuffer
let reference = try getRoot(buffer: buf)!
let json = reference.jsonString()
// swiftformat:disable all
XCTAssertEqual(
json,
"{\"bar\": [1, 2, 3], \"bar3\": [1, 2, 3], \"bool\": true, \"bools\": [true, false, true, false], \"foo\": 100.0, \"mymap\": {\"foo\": \"Fred\"}, \"vec\": [-100, \"Fred\", 4.0, \"M\", false, 4.0]}"
)
// swiftformat:enable all
let data = json.data(using: .utf8)!
let decodedData = try JSONSerialization.jsonObject(
with: data,
options: []) as! [String: Any]
XCTAssertEqual(decodedData["bar"] as! [Int], [1, 2, 3])
XCTAssertEqual(decodedData["bar3"] as! [Int], [1, 2, 3])
let vec: [Any] = decodedData["vec"] as! [Any]
XCTAssertEqual(vec[0] as! Int, -100)
XCTAssertEqual(vec[1] as! String, "Fred")
XCTAssertEqual(vec[2] as! Double, 4.0)
XCTAssertEqual(vec[3] as! String, "M")
XCTAssertEqual(vec[4] as! Bool, false)
XCTAssertEqual(vec[5] as! Double, 4.0)
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Common
import FlexBuffers
import XCTest
final class FlexBuffersReaderTests: XCTestCase {
func testReadingProperBuffer() throws {
let buf: ByteBuffer = createProperBuffer().byteBuffer
try validate(buffer: buf)
}
func testReadingSizedBuffer() throws {
let buf: ByteBuffer = createSizedBuffer()
try validate(buffer: buf)
}
private func validate(buffer buf: ByteBuffer) throws {
let reference = try getRoot(buffer: buf)!
XCTAssertEqual(reference.type, .map)
let map = reference.map!
XCTAssertEqual(map.count, 7)
let vecRef = map["vec"]!
XCTAssertEqual(vecRef.type, .vector)
let vec = vecRef.vector!
XCTAssertEqual(vec.count, 6)
XCTAssertEqual(vec[0]?.type, .int)
XCTAssertEqual(vec[0]?.int, -100)
XCTAssertEqual(vec[1]?.type, .string)
XCTAssertEqual(vec[1]?.cString, "Fred")
XCTAssertNil(vec[1]?.int)
XCTAssertEqual(vec[2]?.double, 4.0)
XCTAssertTrue(vec[3]?.type == .blob)
let blob = vec[3]!.blob { pointer in
Array(pointer)
}
XCTAssertEqual(blob?.count, 1)
XCTAssertEqual(blob?[0], 77)
XCTAssertEqual(vec[4]?.type, .bool)
XCTAssertEqual(vec[4]?.bool, false)
XCTAssertEqual(vec[5]?.double, 4.0) // Shared with vec[2]
let barVec = map["bar"]!.typedVector!
XCTAssertEqual(barVec.count, 3)
XCTAssertEqual(barVec[2]?.int, 3)
XCTAssertEqual(barVec[2]?.asInt(), UInt8(3))
let fixedVec = map["bar3"]!.fixedTypedVector!
XCTAssertEqual(fixedVec.count, 3)
XCTAssertEqual(fixedVec[2]?.int, 3)
XCTAssertEqual(fixedVec[2]?.asInt(), UInt8(3))
XCTAssertEqual(map["bool"]?.bool, true)
let boolsVector = map["bools"]!.typedVector!
XCTAssertEqual(boolsVector.type, .bool)
XCTAssertEqual(boolsVector[0]?.bool, true)
XCTAssertEqual(boolsVector[1]?.bool, false)
let bools = [true, false, true, false]
boolsVector.withUnsafeRawBufferPointer { buff in
for i in 0..<boolsVector.count {
XCTAssertEqual(buff.load(fromByteOffset: i, as: Bool.self), bools[i])
}
}
XCTAssertEqual(map["foo"]?.double, 100)
XCTAssertNil(map["unknown"])
let mymap = map["mymap"]?.map
// Check if both addresses used are the same for keys and strings
XCTAssertEqual(mymap?.keys[0]?.cString, map.keys[4]?.cString)
map.keys[4]?.withUnsafeRawPointer { pointer in
mymap?.keys[0]?.withUnsafeRawPointer { mymapPointer in
XCTAssertEqual(pointer, mymapPointer)
}
}
XCTAssertEqual(mymap?.values[0]?.cString, vec[1]?.cString)
vec[1]?.withUnsafeRawPointer { pointer in
mymap?.values[0]?.withUnsafeRawPointer { mymapPointer in
XCTAssertEqual(pointer, mymapPointer)
}
}
}
private var path: String {
#if os(macOS)
// Gets the current path of this test file then
// strips out the nested directories.
let filePath = URL(filePath: #file)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
return filePath.absoluteString
#else
return FileManager.default.currentDirectoryPath
#endif
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Common
import FlexBuffers
import XCTest
final class FlexBuffersStringTests: XCTestCase {
func testEncodingUnicodeString() {
let text = "プ画をみて✋"
let bytes = text.data(using: .unicode, allowLossyConversion: true)
var flx = FlexBuffersWriter()
flx.map { writer in
writer.add(blob: bytes!, key: "text", length: bytes!.count)
}
flx.finish()
let byteBuffer = flx.sizedByteBuffer
let reference = try! getRoot(buffer: byteBuffer)
let root = reference?.map?["text"]
let builtString = root?.blob {
let data = Data(bytes: $0.baseAddress!, count: Int($0.count))
return String(data: data, encoding: .unicode)
}
XCTAssertEqual(builtString, text)
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Common
import FlexBuffers
import XCTest
final class FlexBuffersWriterTests: XCTestCase {
func testDeallocation() {
let buf: ByteBuffer = {
var fbx = FlexBuffersWriter()
fbx.add(string: "Hello")
fbx.finish()
return fbx.sizedByteBuffer
}()
buf.withUnsafeBytes {
XCTAssertEqual(
Array($0),
[5, 72, 101, 108, 108, 111, 0, 6, 20, 1])
}
}
func testAddingVectorOfScalars() {
var fbx = FlexBuffersWriter()
fbx.vector {
let arr: [Int32] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 20]
$0.create(vector: arr)
}
fbx.finish()
let buf: ByteBuffer = fbx.sizedByteBuffer
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
[10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 20, 0, 0, 0, 1, 41, 46, 2, 40, 1])
// swiftformat:enable all
}
}
func testAddingVectorOfUnsignedScalars() {
var fbx = FlexBuffersWriter()
fbx.vector {
let arr: [UInt] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 20]
$0.create(vector: arr)
}
fbx.finish()
let buf: ByteBuffer = fbx.sizedByteBuffer
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
[10, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 81, 51, 2, 40, 1])
// swiftformat:enable all
}
}
func testAddingVectorOfBools() {
var fbx = FlexBuffersWriter()
fbx.vector {
let arr: [Bool] = [true, false, true, false]
$0.create(vector: arr)
}
fbx.finish()
let buf: ByteBuffer = fbx.sizedByteBuffer
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
[4, 1, 0, 1, 0, 1, 5, 144, 2, 40, 1])
// swiftformat:enable all
}
}
func testSortingWithinMap() {
var fbx = FlexBuffersWriter()
fbx.map {
$0.add(bool: false, key: "bool2")
$0.add(bool: true, key: "bool1")
}
fbx.finish()
let buf: ByteBuffer = fbx.sizedByteBuffer
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
[98, 111, 111, 108, 50, 0, 98, 111, 111, 108, 49, 0, 2, 7, 14, 2, 1, 2, 1, 0, 104, 104, 4, 36, 1]
)
// swiftformat:enable all
}
}
func testSharingKeyWithinMap() {
var fbx = FlexBuffersWriter(initialSize: 1000, flags: .shareKeysAndStrings)
fbx.map {
$0.add(string: "welcome", key: "welcome")
$0.add(string: "welcome", key: "welcome")
$0.add(string: "welcome", key: "welcome")
}
fbx.finish()
let buf: ByteBuffer = fbx.sizedByteBuffer
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
[119, 101, 108, 99, 111, 109, 101, 0, 7, 119, 101, 108, 99, 111, 109, 101, 0, 3, 18, 19, 20, 3, 1, 3, 15, 16, 17, 20, 20, 20, 6, 36, 1]
)
// swiftformat:enable all
}
}
func testNestingVectorInMap() {
let buf: ByteBuffer = createSizedBuffer()
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
flexbufferGolden
)
// swiftformat:enable all
}
}
func testAddingNil() {
var fbx = FlexBuffersWriter(
initialSize: 8,
flags: .shareKeysAndStrings)
fbx.map { map in
map.addNil(key: "v")
}
fbx.finish()
let buf: ByteBuffer = fbx.sizedByteBuffer
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
[118, 0, 1, 3, 1, 1, 1, 0, 0, 2, 36, 1]
)
// swiftformat:enable all
}
}
func testAddingManually() {
var fbx = FlexBuffersWriter(
initialSize: 8,
flags: .shareKeysAndStrings)
let outerMap = fbx.startMap()
let vector = fbx.startVector(key: "vec")
fbx.add(int64: -100)
fbx.add(string: "Fred")
fbx.indirect(float32: 4.0)
let lv = fbx.lastValue()
let blob: [UInt8] = [77]
fbx.add(blob: blob, length: blob.count)
fbx.add(bool: false)
fbx.reuse(value: lv!)
fbx.endVector(start: vector)
let ints: [Int32] = [1, 2, 3]
fbx.create(vector: ints, key: "bar")
fbx.createFixed(vector: ints, key: "bar3")
let bools = [true, false, true, false]
fbx.create(vector: bools, key: "bools")
fbx.add(bool: true, key: "bool")
fbx.add(double: 100, key: "foo")
let innerMap = fbx.startMap(key: "mymap")
fbx.add(string: "Fred", key: "foo")
fbx.endMap(start: innerMap)
fbx.endMap(start: outerMap)
fbx.finish()
let buf: ByteBuffer = fbx.sizedByteBuffer
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
flexbufferGolden
)
// swiftformat:enable all
}
}
func testEncodingAllTypes() {
var fbx = FlexBuffersWriter()
fbx.vector {
$0.indirect(int64: 9)
$0.indirect(uint64: 9)
$0.indirect(float32: 3)
$0.indirect(double: 3)
$0.addNil()
$0.add(bool: true)
$0.add(int64: 9)
$0.add(int64: -9)
$0.add(uint64: 9)
$0.add(double: 2.4)
$0.add(float32: 2.4)
$0.add(double: -2.4)
$0.add(float32: -2.4)
}
fbx.finish()
let buf: ByteBuffer = fbx.sizedByteBuffer
buf.withUnsafeBytes {
// swiftformat:disable all
XCTAssertEqual(
Array($0),
allTypesGolden)
// swiftformat:enable all
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import FlexBuffers
// swiftformat:disable all
let flexbufferGolden: [UInt8] = [118, 101, 99, 0, 4, 70, 114, 101, 100, 0, 0, 0, 0, 0, 128, 64, 1, 77, 6, 156, 15, 9, 5, 0, 12, 4, 20, 34, 100, 104, 34, 98, 97, 114, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 98, 97, 114, 51, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 98, 111, 111, 108, 115, 0, 4, 1, 0, 1, 0, 98, 111, 111, 108, 0, 102, 111, 111, 0, 109, 121, 109, 97, 112, 0, 1, 11, 1, 1, 1, 98, 20, 7, 75, 55, 25, 37, 22, 19, 112, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 88, 0, 0, 0, 72, 0, 0, 0, 1, 0, 0, 0, 61, 0, 0, 0, 0, 0, 200, 66, 45, 0, 0, 0, 133, 0, 0, 0, 46, 78, 106, 144, 14, 36, 40, 35, 38, 1]
let allTypesGolden: [UInt8] = [9, 9, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 247, 255, 255, 255, 255, 255, 255, 255, 9, 0, 0, 0, 0, 0, 0, 0, 51, 51, 51, 51, 51, 51, 3, 64, 0, 0, 0, 64, 51, 51, 3, 64, 51, 51, 51, 51, 51, 51, 3, 192, 0, 0, 0, 64, 51, 51, 3, 192, 24, 28, 34, 34, 3, 107, 7, 7, 11, 15, 15, 15, 15, 117, 43, 1]
// swiftformat:enable all
@inline(__always)
func createSizedBuffer() -> ByteBuffer {
createProperBuffer().sizedByteBuffer
}
@inline(__always)
func createProperBuffer() -> FlexBuffersWriter {
var fbx = FlexBuffersWriter(
initialSize: 8,
flags: .shareKeysAndStrings)
fbx.map { map in
map.vector(key: "vec") { v in
v.add(int64: -100)
v.add(string: "Fred")
v.indirect(float32: 4.0)
let lv = v.lastValue()
let blob: [UInt8] = [77]
v.add(blob: blob, length: blob.count)
v.add(bool: false)
v.reuse(value: lv!)
}
let ints: [Int32] = [1, 2, 3]
map.create(vector: ints, key: "bar")
map.createFixed(vector: ints, key: "bar3")
let bools = [true, false, true, false]
map.create(vector: bools, key: "bools")
map.add(bool: true, key: "bool")
map.add(double: 100, key: "foo")
map.map(key: "mymap") { m in
m.add(string: "Fred", key: "foo")
}
}
fbx.finish()
return fbx
}