Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Sources/NewCodable/GrowableEncodingBytes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ extension GrowableEncodingBytes {
}
}

public var span: Span<UInt8> {
@_lifetime(borrow self)
// @export(implementation)
@inline(__always)
get {
unsafe bytes._unsafeView(as: UInt8.self)
}
}

public var mutableBytes: MutableRawSpan {
@_lifetime(&self)
// @export(implementation)
Expand Down
72 changes: 64 additions & 8 deletions Sources/NewCodable/JSON/NewJSONDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,13 @@ public struct NewJSONDecoder {
self.options = options
}

// TODO: RawSpan-taking functions need to detect Unicode encoding (including BOM) and convert to UTF-8, if necessary.
@usableFromInline
internal func _decode<T: JSONDecodable & ~Copyable>(_ type: T.Type, from data: Data, closure: (inout JSONParserDecoder) throws(CodingError.Decoding) -> T) throws(CodingError.Decoding) -> T {
internal func _decode<T: JSONDecodable & ~Copyable>(_ type: T.Type, from bytes: RawSpan, closure: (inout JSONParserDecoder) throws(CodingError.Decoding) -> T) throws(CodingError.Decoding) -> T {
var node = JSONParserDecoder.CodingPathNode.root
return try withUnsafeMutablePointer(to: &node) { ptr throws(CodingError.Decoding) in
let localOptions = self.options
let parserState = JSONParserDecoder.ParserState(span: data.span.bytes, options: localOptions&, topCodingPathNode: ptr)
let parserState = JSONParserDecoder.ParserState(unvalidatedUTF8Span: bytes, options: localOptions&, topCodingPathNode: ptr)
var inner = JSONParserDecoder(state: parserState)
let result = try closure(&inner)
try inner._finishDecode()
Expand All @@ -127,18 +128,18 @@ public struct NewJSONDecoder {
}

@inlinable
public func decode<T: JSONDecodable & ~Copyable>(_ type: T.Type, from data: Data) throws(CodingError.Decoding) -> T {
try _decode(type, from: data) { inner throws(CodingError.Decoding) in
public func decode<T: JSONDecodable & ~Copyable>(_ type: T.Type, from bytes: RawSpan) throws(CodingError.Decoding) -> T {
try _decode(type, from: bytes) { inner throws(CodingError.Decoding) in
try inner.decode(type)
}
}

@usableFromInline
internal func _decode<T: CommonDecodable>(_ type: T.Type, from data: Data, closure: (inout JSONParserDecoder) throws(CodingError.Decoding) -> T) throws(CodingError.Decoding) -> T {
internal func _decode<T: CommonDecodable>(_ type: T.Type, from bytes: RawSpan, closure: (inout JSONParserDecoder) throws(CodingError.Decoding) -> T) throws(CodingError.Decoding) -> T {
var node = JSONParserDecoder.CodingPathNode.root
return try withUnsafeMutablePointer(to: &node) { ptr throws(CodingError.Decoding) in
let localOptions = self.options
let parserState = JSONParserDecoder.ParserState(span: data.span.bytes, options: localOptions&, topCodingPathNode: ptr)
let parserState = JSONParserDecoder.ParserState(unvalidatedUTF8Span: bytes, options: localOptions&, topCodingPathNode: ptr)
var inner = JSONParserDecoder(state: parserState)
let result = try closure(&inner)
try inner._finishDecode()
Expand All @@ -148,11 +149,66 @@ public struct NewJSONDecoder {

@_disfavoredOverload
@inlinable
public func decode<T: CommonDecodable>(_ type: T.Type, from data: Data) throws(CodingError.Decoding) -> T {
try _decode(type, from: data) { parserDecoder throws(CodingError.Decoding) in
public func decode<T: CommonDecodable>(_ type: T.Type, from bytes: RawSpan) throws(CodingError.Decoding) -> T {
try _decode(type, from: bytes) { parserDecoder throws(CodingError.Decoding) in
try parserDecoder.decode(type)
}
}

@usableFromInline
internal func _decode<T: JSONDecodable & ~Copyable>(_ type: T.Type, from utf8: UTF8Span, closure: (inout JSONParserDecoder) throws(CodingError.Decoding) -> T) throws(CodingError.Decoding) -> T {
var node = JSONParserDecoder.CodingPathNode.root
return try withUnsafeMutablePointer(to: &node) { ptr throws(CodingError.Decoding) in
let localOptions = self.options
let parserState = JSONParserDecoder.ParserState(utf8: utf8, options: localOptions&, topCodingPathNode: ptr)
var inner = JSONParserDecoder(state: parserState)
let result = try closure(&inner)
try inner._finishDecode()
return result
}
}

@inlinable
public func decode<T: JSONDecodable & ~Copyable>(_ type: T.Type, from utf8: UTF8Span) throws(CodingError.Decoding) -> T {
try _decode(type, from: utf8) { inner throws(CodingError.Decoding) in
try inner.decode(type)
}
}

@usableFromInline
internal func _decode<T: CommonDecodable>(_ type: T.Type, from utf8: UTF8Span, closure: (inout JSONParserDecoder) throws(CodingError.Decoding) -> T) throws(CodingError.Decoding) -> T {
var node = JSONParserDecoder.CodingPathNode.root
return try withUnsafeMutablePointer(to: &node) { ptr throws(CodingError.Decoding) in
let localOptions = self.options
let parserState = JSONParserDecoder.ParserState(utf8: utf8, options: localOptions&, topCodingPathNode: ptr)
var inner = JSONParserDecoder(state: parserState)
let result = try closure(&inner)
try inner._finishDecode()
return result
}
}

@_disfavoredOverload
@inlinable
public func decode<T: CommonDecodable>(_ type: T.Type, from utf8: UTF8Span) throws(CodingError.Decoding) -> T {
try _decode(type, from: utf8) { parserDecoder throws(CodingError.Decoding) in
try parserDecoder.decode(type)
}
}
}

// TODO: Move to Foundation + JSON cross-module import.
extension NewJSONDecoder {
@_alwaysEmitIntoClient
public func decode<T: JSONDecodable & ~Copyable>(_ type: T.Type, from data: Data) throws(CodingError.Decoding) -> T {
try self.decode(type, from: data.bytes)
}

@_disfavoredOverload
@_alwaysEmitIntoClient
public func decode<T: CommonDecodable>(_ type: T.Type, from data: Data) throws(CodingError.Decoding) -> T {
try self.decode(type, from: data.bytes)
}
}

@usableFromInline
Expand Down
63 changes: 49 additions & 14 deletions Sources/NewCodable/JSON/NewJSONEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public struct NewJSONEncoder {
internal var nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy
internal var withoutEscapingSlashes: Bool
internal var pretty: Bool

public init(assumesTopLevelDictionary: Bool = false,
dataEncodingStrategy: DataEncodingStrategy = .base64,
dateEncodingStrategy: DateEncodingStrategy = .deferredToDate,
Expand All @@ -151,16 +151,55 @@ public struct NewJSONEncoder {
self.options = options
}

public func encode(_ value: borrowing some JSONEncodable & ~Copyable) throws(CodingError.Encoding) -> Data {
internal func encode(_ value: borrowing some JSONEncodable & ~Copyable) throws(CodingError.Encoding) -> GrowableEncodingBytes {
var rootNodeArray: InlineArray = [JSONDirectEncoder.CodingPathNode.root]
var nodeSpan = rootNodeArray.mutableSpan
return try nodeSpan.withUnsafeMutableBufferPointer { ptr throws(CodingError.Encoding) in
var inner = JSONDirectEncoder(options: self.options, topCodingPathNode: ptr.baseAddress!)
try value.encode(to: &inner)
return inner.takeData()
return inner.takeBytes()
}
}

internal func encode(_ value: borrowing some CommonEncodable & ~Copyable) throws(CodingError.Encoding) -> GrowableEncodingBytes {
var rootNodeArray: InlineArray = [JSONDirectEncoder.CodingPathNode.root]
var nodeSpan = rootNodeArray.mutableSpan
return try nodeSpan.withUnsafeMutableBufferPointer { ptr throws(CodingError.Encoding) in
var inner = JSONDirectEncoder(options: self.options, topCodingPathNode: ptr.baseAddress!)
try value.encode(to: &inner)
return inner.takeBytes()
}
}

// TODO: Replace with a more desirable span-based interface
public func encode<T: ~Copyable>(_ value: borrowing some JSONEncodable & ~Copyable, _ resultSpanClosure: (RawSpan) throws -> T) throws -> T {
let bytes: GrowableEncodingBytes = try self.encode(value)
return try resultSpanClosure(bytes.span.bytes)
}

public func encode<T: ~Copyable>(_ value: borrowing some JSONEncodable & CommonEncodable & ~Copyable, _ resultSpanClosure: (RawSpan) throws -> T) throws -> T {
@_transparent func asJSON<TAsJSON: JSONEncodable & ~Copyable>(_ value: borrowing TAsJSON) throws -> T {
try self.encode(value, resultSpanClosure)
}
return try asJSON(value)
}

public func encode<T: ~Copyable>(_ value: borrowing some CommonEncodable & ~Copyable, _ resultSpanClosure: (RawSpan) throws -> T) throws -> T {
let bytes: GrowableEncodingBytes = try self.encode(value)
return try resultSpanClosure(bytes.span.bytes)
}
}

// TODO: Move to Foundation + JSON cross-module import.
extension NewJSONEncoder {
public func encode(_ value: borrowing some JSONEncodable & ~Copyable) throws(CodingError.Encoding) -> Data {
let bytes: GrowableEncodingBytes = try self.encode(value)

let (storage, count) = bytes.deconstruct()
guard let pointer = storage.baseAddress else { return Data() }
return Data(bytesNoCopy: pointer, count: count, deallocator: .custom({ptr, _ in ptr.deallocate() }))
}

public func encode(_ value: borrowing some JSONEncodable & CommonEncodable & ~Copyable) throws(CodingError.Encoding) -> Data {
@_transparent func asJSON<TAsJSON: JSONEncodable & ~Copyable>(_ value: borrowing TAsJSON) throws(CodingError.Encoding) -> Data {
try self.encode(value)
Expand All @@ -169,13 +208,11 @@ public struct NewJSONEncoder {
}

public func encode(_ value: borrowing some CommonEncodable & ~Copyable) throws(CodingError.Encoding) -> Data {
var rootNodeArray: InlineArray = [JSONDirectEncoder.CodingPathNode.root]
var nodeSpan = rootNodeArray.mutableSpan
return try nodeSpan.withUnsafeMutableBufferPointer { ptr throws(CodingError.Encoding) in
var inner = JSONDirectEncoder(options: self.options, topCodingPathNode: ptr.baseAddress!)
try value.encode(to: &inner)
return inner.takeData()
}
let bytes: GrowableEncodingBytes = try self.encode(value)

let (storage, count) = bytes.deconstruct()
guard let pointer = storage.baseAddress else { return Data() }
return Data(bytesNoCopy: pointer, count: count, deallocator: .custom({ptr, _ in ptr.deallocate() }))
}
}

Expand Down Expand Up @@ -286,10 +323,8 @@ public struct JSONDirectEncoder: CommonEncoder, ~Copyable, ~Escapable {
self.state.currentTopCodingPathNode.pointee.printCodingPathAddrs()
}

consuming func takeData() -> Data {
let (storage, count) = self.state.writer.data.deconstruct()
guard let pointer = storage.baseAddress else { return Data() }
return Data(bytesNoCopy: pointer, count: count, deallocator: .custom({ptr, _ in ptr.deallocate() }))
consuming func takeBytes() -> GrowableEncodingBytes {
return self.state.writer.data
}

public mutating func encodeNil() throws(CodingError.Encoding) {
Expand Down
36 changes: 32 additions & 4 deletions Sources/NewCodable/JSON/ParserState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,20 @@ extension JSONParserDecoder {

@inlinable
@_lifetime(copy span, copy options)
init(span: RawSpan, options: _Borrow<NewJSONDecoder.Options>, topCodingPathNode: UnsafeMutablePointer<CodingPathNode>) {
init(unvalidatedUTF8Span span: RawSpan, options: _Borrow<NewJSONDecoder.Options>, topCodingPathNode: UnsafeMutablePointer<CodingPathNode>) {
self.reader = .init(bytes: span)
self.options = options
self.currentTopCodingPathNode = topCodingPathNode
}

@inlinable
@_lifetime(copy utf8, copy options)
init(utf8: UTF8Span, options: _Borrow<NewJSONDecoder.Options>, topCodingPathNode: UnsafeMutablePointer<CodingPathNode>) {
self.reader = .init(utf8: utf8)
self.options = options
self.currentTopCodingPathNode = topCodingPathNode
}

@usableFromInline
var codingPath: CodingPath {
self.currentTopCodingPathNode.pointee.path
Expand Down Expand Up @@ -273,9 +281,11 @@ extension JSONParserDecoder {
@frozen
public struct DocumentReader: ~Escapable {

// TODO: UTF8Span?
@usableFromInline
let bytes: RawSpan

@usableFromInline
let utf8Validated: Bool

@usableFromInline
internal var readOffset : Int
Expand Down Expand Up @@ -320,6 +330,15 @@ extension JSONParserDecoder {
init(bytes: RawSpan) {
self.bytes = bytes
self.readOffset = 0
self.utf8Validated = false
}

@inlinable
@_lifetime(copy utf8)
init(utf8: UTF8Span) {
self.bytes = utf8.span.bytes
self.readOffset = 0
self.utf8Validated = true
}

@inlinable
Expand Down Expand Up @@ -929,7 +948,11 @@ extension JSONParserDecoder {
let firstSectionSubspan = bytes.extracting(unchecked: startIndex..<readOffset-1)
let firstSectionUTF8Span: UTF8Span
do {
firstSectionUTF8Span = try UTF8Span(validating: .init(_bytes: firstSectionSubspan))
if utf8Validated {
firstSectionUTF8Span = UTF8Span(unchecked: .init(_bytes: firstSectionSubspan), isKnownASCII: false)
} else {
firstSectionUTF8Span = try UTF8Span(validating: .init(_bytes: firstSectionSubspan))
}
} catch {
// TODO: This source location doesn't work any more.
throw .cannotConvertInputStringDataToUTF8(location: .countingLinesAndColumns(upTo: startIndex, in: bytes))
Expand All @@ -953,7 +976,12 @@ extension JSONParserDecoder {

do {
// TODO: Creation of the String should be deferred until we know that the DecodingField or DecodingStringVisitor client wants a `String`. We could easily just give them the UTF8Span (or byte span?) instead.
let utf8Span = try UTF8Span(validating: buffer.span)
let span = buffer.span
let utf8Span: UTF8Span = if utf8Validated {
UTF8Span(unchecked: span, isKnownASCII: false)
} else {
try UTF8Span(validating: span)
}
let output = firstStringChunk + String(copying: utf8Span)

let subspan = bytes.extracting(unchecked: Range(uncheckedBounds: (startIndex, readOffset-1)))
Expand Down
6 changes: 1 addition & 5 deletions Sources/NewCodable/SharedTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,8 @@ public extension DecodingStringVisitor where Self: ~Copyable & ~Escapable {
// TODO: watchOS/32-bit.
return try visitUTF8Bytes(string.utf8Span)
}
// TODO: Temporary compatibility
func visitUTF8Bytes(_ buffer: UnsafeBufferPointer<UInt8>) throws(CodingError.Decoding) -> DecodedValue {
let span = UTF8Span(unchecked: Span(_unsafeElements: buffer))
return try visitUTF8Bytes(span)
}
}

/// A type that will visit a collection of encoded bytes from a decoder to produce a decoded value.
public protocol DecodingBytesVisitor: BaseDecodingVisitor & ~Copyable & ~Escapable {
/// Produces a `DecodedValue` from the `RawSpan` encountered by the decoder
Expand Down
Loading