Coverage Report

Created: 2026-11-12 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-protobuf/Sources/SwiftProtobuf/TextFormatEncoder.swift
Line
Count
Source
1
// Sources/SwiftProtobuf/TextFormatEncoder.swift - Text format encoding support
2
//
3
// Copyright (c) 2014 - 2019 Apple Inc. and the project authors
4
// Licensed under Apache License v2.0 with Runtime Library Exception
5
//
6
// See LICENSE.txt for license information:
7
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
8
//
9
// -----------------------------------------------------------------------------
10
///
11
/// Text format serialization engine.
12
///
13
// -----------------------------------------------------------------------------
14
15
#if canImport(FoundationEssentials)
16
import FoundationEssentials
17
#else
18
import Foundation
19
#endif
20
21
private let asciiSpace = UInt8(ascii: " ")
22
private let asciiColon = UInt8(ascii: ":")
23
private let asciiComma = UInt8(ascii: ",")
24
private let asciiMinus = UInt8(ascii: "-")
25
private let asciiBackslash = UInt8(ascii: "\\")
26
private let asciiDoubleQuote = UInt8(ascii: "\"")
27
private let asciiZero = UInt8(ascii: "0")
28
private let asciiOpenCurlyBracket = UInt8(ascii: "{")
29
private let asciiCloseCurlyBracket = UInt8(ascii: "}")
30
private let asciiOpenSquareBracket = UInt8(ascii: "[")
31
private let asciiCloseSquareBracket = UInt8(ascii: "]")
32
private let asciiNewline = UInt8(ascii: "\n")
33
private let asciiUpperA = UInt8(ascii: "A")
34
35
private let tabSize = 2
36
private let tab = [UInt8](repeating: asciiSpace, count: tabSize)
37
38
/// TextFormatEncoder has no public members.
39
internal struct TextFormatEncoder {
40
2.40k
    private var data = [UInt8]()
41
2.40k
    private var indentString: [UInt8] = []
42
    var stringResult: String {
43
12
        get {
44
12
            String(decoding: data, as: UTF8.self)
45
12
        }
46
    }
47
48
3.55M
    internal mutating func append(staticText: StaticString) {
49
3.55M
        let buff = UnsafeBufferPointer(start: staticText.utf8Start, count: staticText.utf8CodeUnitCount)
50
3.55M
        data.append(contentsOf: buff)
51
3.55M
    }
52
53
0
    internal mutating func append(name: _NameMap.Name) {
54
0
        data.append(contentsOf: name.utf8Buffer)
55
0
    }
56
57
0
    internal mutating func append(bytes: [UInt8]) {
58
0
        data.append(contentsOf: bytes)
59
0
    }
60
61
0
    private mutating func append(text: String) {
62
0
        data.append(contentsOf: text.utf8)
63
0
    }
64
65
12
    init() {}
66
67
1.99M
    internal mutating func indent() {
68
1.99M
        data.append(contentsOf: indentString)
69
1.99M
    }
70
71
110k
    mutating func emitFieldName(name: UnsafeRawBufferPointer) {
72
110k
        indent()
73
110k
        data.append(contentsOf: name)
74
110k
    }
75
76
73.4k
    mutating func emitFieldName(name: StaticString) {
77
73.4k
        let buff = UnsafeRawBufferPointer(start: name.utf8Start, count: name.utf8CodeUnitCount)
78
73.4k
        emitFieldName(name: buff)
79
73.4k
    }
80
81
37.2k
    mutating func emitFieldName(name: [UInt8]) {
82
37.2k
        indent()
83
37.2k
        data.append(contentsOf: name)
84
37.2k
    }
85
86
0
    mutating func emitExtensionFieldName(name: String) {
87
0
        indent()
88
0
        data.append(asciiOpenSquareBracket)
89
0
        append(text: name)
90
0
        data.append(asciiCloseSquareBracket)
91
0
    }
92
93
0
    mutating func emitFieldNumber(number: Int) {
94
0
        indent()
95
0
        appendUInt(value: UInt64(number))
96
0
    }
97
98
666k
    mutating func startRegularField() {
99
666k
        append(staticText: ": ")
100
666k
    }
101
666k
    mutating func endRegularField() {
102
666k
        data.append(asciiNewline)
103
666k
    }
104
105
    // In Text format, a message-valued field writes the name
106
    // without a trailing colon:
107
    //    name_of_field {key: value key2: value2}
108
74.0k
    mutating func startMessageField() {
109
74.0k
        append(staticText: " {\n")
110
74.0k
        indentString.append(contentsOf: tab)
111
74.0k
    }
112
113
296k
    mutating func endMessageField() {
114
296k
        indentString.removeLast(tabSize)
115
296k
        indent()
116
296k
        append(staticText: "}\n")
117
296k
    }
118
119
0
    mutating func startArray() {
120
0
        data.append(asciiOpenSquareBracket)
121
0
    }
122
123
0
    mutating func arraySeparator() {
124
0
        append(staticText: ", ")
125
0
    }
126
127
0
    mutating func endArray() {
128
0
        data.append(asciiCloseSquareBracket)
129
0
    }
130
131
0
    mutating func putEnumValue<E: Enum>(value: E) {
132
0
        if let name = value.name {
133
0
            data.append(contentsOf: name.utf8Buffer)
134
0
        } else {
135
0
            appendInt(value: Int64(Int32(truncatingIfNeeded: value.rawValue)))
136
0
        }
137
0
    }
138
139
0
    mutating func putFloatValue(value: Float) {
140
0
        if value.isNaN {
141
0
            append(staticText: "nan")
142
0
        } else if !value.isFinite {
143
0
            if value < 0 {
144
0
                append(staticText: "-inf")
145
0
            } else {
146
0
                append(staticText: "inf")
147
0
            }
148
0
        } else {
149
0
            data.append(contentsOf: value.debugDescription.utf8)
150
0
        }
151
0
    }
152
153
0
    mutating func putDoubleValue(value: Double) {
154
0
        if value.isNaN {
155
0
            append(staticText: "nan")
156
0
        } else if !value.isFinite {
157
0
            if value < 0 {
158
0
                append(staticText: "-inf")
159
0
            } else {
160
0
                append(staticText: "inf")
161
0
            }
162
0
        } else {
163
0
            data.append(contentsOf: value.debugDescription.utf8)
164
0
        }
165
0
    }
166
167
126k
    private mutating func appendUInt(value: UInt64) {
168
126k
        if value >= 1000 {
169
1.87k
            appendUInt(value: value / 1000)
170
1.87k
        }
171
126k
        if value >= 100 {
172
2.33k
            data.append(asciiZero + UInt8((value / 100) % 10))
173
2.33k
        }
174
126k
        if value >= 10 {
175
28.8k
            data.append(asciiZero + UInt8((value / 10) % 10))
176
28.8k
        }
177
126k
        data.append(asciiZero + UInt8(value % 10))
178
126k
    }
179
171k
    private mutating func appendInt(value: Int64) {
180
171k
        if value < 0 {
181
145
            data.append(asciiMinus)
182
145
            // This is the twos-complement negation of value,
183
145
            // computed in a way that won't overflow a 64-bit
184
145
            // signed integer.
185
145
            appendUInt(value: 1 + ~UInt64(bitPattern: value))
186
170k
        } else {
187
170k
            appendUInt(value: UInt64(bitPattern: value))
188
170k
        }
189
171k
    }
190
191
149k
    mutating func putInt64(value: Int64) {
192
149k
        appendInt(value: value)
193
149k
    }
194
195
146k
    mutating func putUInt64(value: UInt64) {
196
146k
        appendUInt(value: value)
197
146k
    }
198
199
0
    mutating func appendUIntHex(value: UInt64, digits: Int) {
200
0
        if digits == 0 {
201
0
            append(staticText: "0x")
202
0
        } else {
203
0
            appendUIntHex(value: value >> 4, digits: digits - 1)
204
0
            let d = UInt8(truncatingIfNeeded: value % 16)
205
0
            data.append(d < 10 ? asciiZero + d : asciiUpperA + d - 10)
206
0
        }
207
0
    }
208
209
0
    mutating func putUInt64Hex(value: UInt64, digits: Int) {
210
0
        appendUIntHex(value: value, digits: digits)
211
0
    }
212
213
0
    mutating func putBoolValue(value: Bool) {
214
0
        append(staticText: value ? "true" : "false")
215
0
    }
216
217
19
    mutating func putStringValue(value: String) {
218
19
        data.append(asciiDoubleQuote)
219
147
        for c in value.unicodeScalars {
220
147
            switch c.value {
221
147
            // Special two-byte escapes
222
147
            case 8:
223
0
                append(staticText: "\\b")
224
147
            case 9:
225
0
                append(staticText: "\\t")
226
147
            case 10:
227
14
                append(staticText: "\\n")
228
147
            case 11:
229
0
                append(staticText: "\\v")
230
147
            case 12:
231
0
                append(staticText: "\\f")
232
147
            case 13:
233
0
                append(staticText: "\\r")
234
147
            case 34:
235
0
                append(staticText: "\\\"")
236
147
            case 92:
237
0
                append(staticText: "\\\\")
238
147
            case 0...31, 127:  // Octal form for C0 control chars
239
43
                data.append(asciiBackslash)
240
43
                data.append(asciiZero + UInt8(c.value / 64))
241
43
                data.append(asciiZero + UInt8(c.value / 8 % 8))
242
43
                data.append(asciiZero + UInt8(c.value % 8))
243
147
            case 0...127:  // ASCII
244
45
                data.append(UInt8(truncatingIfNeeded: c.value))
245
147
            case 0x80...0x7ff:
246
45
                data.append(0xc0 + UInt8(c.value / 64))
247
45
                data.append(0x80 + UInt8(c.value % 64))
248
147
            case 0x800...0xffff:
249
0
                data.append(0xe0 + UInt8(truncatingIfNeeded: c.value >> 12))
250
0
                data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f))
251
0
                data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
252
147
            default:
253
0
                data.append(0xf0 + UInt8(truncatingIfNeeded: c.value >> 18))
254
0
                data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 12) & 0x3f))
255
0
                data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f))
256
0
                data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
257
147
            }
258
147
        }
259
19
        data.append(asciiDoubleQuote)
260
19
    }
261
262
0
    mutating func putBytesValue(value: Data) {
263
0
        data.append(asciiDoubleQuote)
264
609
        value.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
265
609
            if let p = body.baseAddress, body.count > 0 {
266
1.83M
                for i in 0..<body.count {
267
1.83M
                    let c = p[i]
268
1.83M
                    switch c {
269
1.83M
                    // Special two-byte escapes
270
1.83M
                    case 8:
271
678
                        append(staticText: "\\b")
272
1.83M
                    case 9:
273
1.23k
                        append(staticText: "\\t")
274
1.83M
                    case 10:
275
290
                        append(staticText: "\\n")
276
1.83M
                    case 11:
277
1.01k
                        append(staticText: "\\v")
278
1.83M
                    case 12:
279
614
                        append(staticText: "\\f")
280
1.83M
                    case 13:
281
152
                        append(staticText: "\\r")
282
1.83M
                    case 34:
283
500
                        append(staticText: "\\\"")
284
1.83M
                    case 92:
285
1.58k
                        append(staticText: "\\\\")
286
1.83M
                    case 32...126:  // printable ASCII
287
175k
                        data.append(c)
288
1.83M
                    default:  // Octal form for non-printable chars
289
1.65M
                        data.append(asciiBackslash)
290
1.65M
                        data.append(asciiZero + UInt8(c / 64))
291
1.65M
                        data.append(asciiZero + UInt8(c / 8 % 8))
292
1.65M
                        data.append(asciiZero + UInt8(c % 8))
293
1.83M
                    }
294
1.83M
                }
295
609
            }
296
609
        }
297
0
        data.append(asciiDoubleQuote)
298
0
    }
299
}