Signal-Pods/SwiftProtobuf/Sources/SwiftProtobuf/TextFormatEncoder.swift
2026-03-17 12:48:37 -05:00

300 lines
9.6 KiB
Swift

// Sources/SwiftProtobuf/TextFormatEncoder.swift - Text format encoding support
//
// Copyright (c) 2014 - 2019 Apple Inc. and the project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See LICENSE.txt for license information:
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
//
// -----------------------------------------------------------------------------
///
/// Text format serialization engine.
///
// -----------------------------------------------------------------------------
#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
private let asciiSpace = UInt8(ascii: " ")
private let asciiColon = UInt8(ascii: ":")
private let asciiComma = UInt8(ascii: ",")
private let asciiMinus = UInt8(ascii: "-")
private let asciiBackslash = UInt8(ascii: "\\")
private let asciiDoubleQuote = UInt8(ascii: "\"")
private let asciiZero = UInt8(ascii: "0")
private let asciiOpenCurlyBracket = UInt8(ascii: "{")
private let asciiCloseCurlyBracket = UInt8(ascii: "}")
private let asciiOpenSquareBracket = UInt8(ascii: "[")
private let asciiCloseSquareBracket = UInt8(ascii: "]")
private let asciiNewline = UInt8(ascii: "\n")
private let asciiUpperA = UInt8(ascii: "A")
private let tabSize = 2
private let tab = [UInt8](repeating: asciiSpace, count: tabSize)
/// TextFormatEncoder has no public members.
internal struct TextFormatEncoder {
private var data = [UInt8]()
private var indentString: [UInt8] = []
var stringResult: String {
get {
String(decoding: data, as: UTF8.self)
}
}
internal mutating func append(staticText: StaticString) {
let buff = UnsafeBufferPointer(start: staticText.utf8Start, count: staticText.utf8CodeUnitCount)
data.append(contentsOf: buff)
}
internal mutating func append(name: _NameMap.Name) {
data.append(contentsOf: name.utf8Buffer)
}
internal mutating func append(bytes: [UInt8]) {
data.append(contentsOf: bytes)
}
private mutating func append(text: String) {
data.append(contentsOf: text.utf8)
}
init() {}
internal mutating func indent() {
data.append(contentsOf: indentString)
}
mutating func emitFieldName(name: UnsafeRawBufferPointer) {
indent()
data.append(contentsOf: name)
}
mutating func emitFieldName(name: StaticString) {
let buff = UnsafeRawBufferPointer(start: name.utf8Start, count: name.utf8CodeUnitCount)
emitFieldName(name: buff)
}
mutating func emitFieldName(name: [UInt8]) {
indent()
data.append(contentsOf: name)
}
mutating func emitExtensionFieldName(name: String) {
indent()
data.append(asciiOpenSquareBracket)
append(text: name)
data.append(asciiCloseSquareBracket)
}
mutating func emitFieldNumber(number: Int) {
indent()
appendUInt(value: UInt64(number))
}
mutating func startRegularField() {
append(staticText: ": ")
}
mutating func endRegularField() {
data.append(asciiNewline)
}
// In Text format, a message-valued field writes the name
// without a trailing colon:
// name_of_field {key: value key2: value2}
mutating func startMessageField() {
append(staticText: " {\n")
indentString.append(contentsOf: tab)
}
mutating func endMessageField() {
indentString.removeLast(tabSize)
indent()
append(staticText: "}\n")
}
mutating func startArray() {
data.append(asciiOpenSquareBracket)
}
mutating func arraySeparator() {
append(staticText: ", ")
}
mutating func endArray() {
data.append(asciiCloseSquareBracket)
}
mutating func putEnumValue<E: Enum>(value: E) {
if let name = value.name {
data.append(contentsOf: name.utf8Buffer)
} else {
appendInt(value: Int64(value.rawValue))
}
}
mutating func putFloatValue(value: Float) {
if value.isNaN {
append(staticText: "nan")
} else if !value.isFinite {
if value < 0 {
append(staticText: "-inf")
} else {
append(staticText: "inf")
}
} else {
data.append(contentsOf: value.debugDescription.utf8)
}
}
mutating func putDoubleValue(value: Double) {
if value.isNaN {
append(staticText: "nan")
} else if !value.isFinite {
if value < 0 {
append(staticText: "-inf")
} else {
append(staticText: "inf")
}
} else {
data.append(contentsOf: value.debugDescription.utf8)
}
}
private mutating func appendUInt(value: UInt64) {
if value >= 1000 {
appendUInt(value: value / 1000)
}
if value >= 100 {
data.append(asciiZero + UInt8((value / 100) % 10))
}
if value >= 10 {
data.append(asciiZero + UInt8((value / 10) % 10))
}
data.append(asciiZero + UInt8(value % 10))
}
private mutating func appendInt(value: Int64) {
if value < 0 {
data.append(asciiMinus)
// This is the twos-complement negation of value,
// computed in a way that won't overflow a 64-bit
// signed integer.
appendUInt(value: 1 + ~UInt64(bitPattern: value))
} else {
appendUInt(value: UInt64(bitPattern: value))
}
}
mutating func putInt64(value: Int64) {
appendInt(value: value)
}
mutating func putUInt64(value: UInt64) {
appendUInt(value: value)
}
mutating func appendUIntHex(value: UInt64, digits: Int) {
if digits == 0 {
append(staticText: "0x")
} else {
appendUIntHex(value: value >> 4, digits: digits - 1)
let d = UInt8(truncatingIfNeeded: value % 16)
data.append(d < 10 ? asciiZero + d : asciiUpperA + d - 10)
}
}
mutating func putUInt64Hex(value: UInt64, digits: Int) {
appendUIntHex(value: value, digits: digits)
}
mutating func putBoolValue(value: Bool) {
append(staticText: value ? "true" : "false")
}
mutating func putStringValue(value: String) {
data.append(asciiDoubleQuote)
for c in value.unicodeScalars {
switch c.value {
// Special two-byte escapes
case 8:
append(staticText: "\\b")
case 9:
append(staticText: "\\t")
case 10:
append(staticText: "\\n")
case 11:
append(staticText: "\\v")
case 12:
append(staticText: "\\f")
case 13:
append(staticText: "\\r")
case 34:
append(staticText: "\\\"")
case 92:
append(staticText: "\\\\")
case 0...31, 127: // Octal form for C0 control chars
data.append(asciiBackslash)
data.append(asciiZero + UInt8(c.value / 64))
data.append(asciiZero + UInt8(c.value / 8 % 8))
data.append(asciiZero + UInt8(c.value % 8))
case 0...127: // ASCII
data.append(UInt8(truncatingIfNeeded: c.value))
case 0x80...0x7ff:
data.append(0xc0 + UInt8(c.value / 64))
data.append(0x80 + UInt8(c.value % 64))
case 0x800...0xffff:
data.append(0xe0 + UInt8(truncatingIfNeeded: c.value >> 12))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
default:
data.append(0xf0 + UInt8(truncatingIfNeeded: c.value >> 18))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 12) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
}
}
data.append(asciiDoubleQuote)
}
mutating func putBytesValue(value: Data) {
data.append(asciiDoubleQuote)
value.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
if let p = body.baseAddress, body.count > 0 {
for i in 0..<body.count {
let c = p[i]
switch c {
// Special two-byte escapes
case 8:
append(staticText: "\\b")
case 9:
append(staticText: "\\t")
case 10:
append(staticText: "\\n")
case 11:
append(staticText: "\\v")
case 12:
append(staticText: "\\f")
case 13:
append(staticText: "\\r")
case 34:
append(staticText: "\\\"")
case 92:
append(staticText: "\\\\")
case 32...126: // printable ASCII
data.append(c)
default: // Octal form for non-printable chars
data.append(asciiBackslash)
data.append(asciiZero + UInt8(c / 64))
data.append(asciiZero + UInt8(c / 8 % 8))
data.append(asciiZero + UInt8(c % 8))
}
}
}
}
data.append(asciiDoubleQuote)
}
}