/src/swift-protobuf/Sources/SwiftProtobuf/TextFormatEncoder.swift
Line | Count | Source (jump to first uncovered line) |
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 | | import Foundation |
16 | | |
17 | | private let asciiSpace = UInt8(ascii: " ") |
18 | | private let asciiColon = UInt8(ascii: ":") |
19 | | private let asciiComma = UInt8(ascii: ",") |
20 | | private let asciiMinus = UInt8(ascii: "-") |
21 | | private let asciiBackslash = UInt8(ascii: "\\") |
22 | | private let asciiDoubleQuote = UInt8(ascii: "\"") |
23 | | private let asciiZero = UInt8(ascii: "0") |
24 | | private let asciiOpenCurlyBracket = UInt8(ascii: "{") |
25 | | private let asciiCloseCurlyBracket = UInt8(ascii: "}") |
26 | | private let asciiOpenSquareBracket = UInt8(ascii: "[") |
27 | | private let asciiCloseSquareBracket = UInt8(ascii: "]") |
28 | | private let asciiNewline = UInt8(ascii: "\n") |
29 | | private let asciiUpperA = UInt8(ascii: "A") |
30 | | |
31 | | private let tabSize = 2 |
32 | | private let tab = [UInt8](repeating: asciiSpace, count: tabSize) |
33 | | |
34 | | /// TextFormatEncoder has no public members. |
35 | | internal struct TextFormatEncoder { |
36 | 22.0k | private var data = [UInt8]() |
37 | 22.0k | private var indentString: [UInt8] = [] |
38 | | var stringResult: String { |
39 | 12.1k | get { |
40 | 12.1k | String(decoding: data, as: UTF8.self) |
41 | 12.1k | } |
42 | | } |
43 | | |
44 | 564M | internal mutating func append(staticText: StaticString) { |
45 | 564M | let buff = UnsafeBufferPointer(start: staticText.utf8Start, count: staticText.utf8CodeUnitCount) |
46 | 564M | data.append(contentsOf: buff) |
47 | 564M | } |
48 | | |
49 | 0 | internal mutating func append(name: _NameMap.Name) { |
50 | 0 | data.append(contentsOf: name.utf8Buffer) |
51 | 0 | } |
52 | | |
53 | 0 | internal mutating func append(bytes: [UInt8]) { |
54 | 0 | data.append(contentsOf: bytes) |
55 | 0 | } |
56 | | |
57 | 99.6k | private mutating func append(text: String) { |
58 | 99.6k | data.append(contentsOf: text.utf8) |
59 | 99.6k | } |
60 | | |
61 | 12.1k | init() {} |
62 | | |
63 | 290M | internal mutating func indent() { |
64 | 290M | data.append(contentsOf: indentString) |
65 | 290M | } |
66 | | |
67 | 5.88M | mutating func emitFieldName(name: UnsafeRawBufferPointer) { |
68 | 5.88M | indent() |
69 | 5.88M | data.append(contentsOf: name) |
70 | 5.88M | } |
71 | | |
72 | 475k | mutating func emitFieldName(name: StaticString) { |
73 | 475k | let buff = UnsafeRawBufferPointer(start: name.utf8Start, count: name.utf8CodeUnitCount) |
74 | 475k | emitFieldName(name: buff) |
75 | 475k | } |
76 | | |
77 | 876k | mutating func emitFieldName(name: [UInt8]) { |
78 | 876k | indent() |
79 | 876k | data.append(contentsOf: name) |
80 | 876k | } |
81 | | |
82 | 72.5k | mutating func emitExtensionFieldName(name: String) { |
83 | 72.5k | indent() |
84 | 72.5k | data.append(asciiOpenSquareBracket) |
85 | 72.5k | append(text: name) |
86 | 72.5k | data.append(asciiCloseSquareBracket) |
87 | 72.5k | } |
88 | | |
89 | 17.6M | mutating func emitFieldNumber(number: Int) { |
90 | 17.6M | indent() |
91 | 17.6M | appendUInt(value: UInt64(number)) |
92 | 17.6M | } |
93 | | |
94 | 70.4M | mutating func startRegularField() { |
95 | 70.4M | append(staticText: ": ") |
96 | 70.4M | } |
97 | 125M | mutating func endRegularField() { |
98 | 125M | data.append(asciiNewline) |
99 | 125M | } |
100 | | |
101 | | // In Text format, a message-valued field writes the name |
102 | | // without a trailing colon: |
103 | | // name_of_field {key: value key2: value2} |
104 | 12.2M | mutating func startMessageField() { |
105 | 12.2M | append(staticText: " {\n") |
106 | 12.2M | indentString.append(contentsOf: tab) |
107 | 12.2M | } |
108 | | |
109 | 48.8M | mutating func endMessageField() { |
110 | 48.8M | indentString.removeLast(tabSize) |
111 | 48.8M | indent() |
112 | 48.8M | append(staticText: "}\n") |
113 | 48.8M | } |
114 | | |
115 | 151k | mutating func startArray() { |
116 | 151k | data.append(asciiOpenSquareBracket) |
117 | 151k | } |
118 | | |
119 | 566k | mutating func arraySeparator() { |
120 | 566k | append(staticText: ", ") |
121 | 566k | } |
122 | | |
123 | 151k | mutating func endArray() { |
124 | 151k | data.append(asciiCloseSquareBracket) |
125 | 151k | } |
126 | | |
127 | 60.0k | mutating func putEnumValue<E: Enum>(value: E) { |
128 | 60.0k | if let name = value.name { |
129 | 53.8k | data.append(contentsOf: name.utf8Buffer) |
130 | 60.0k | } else { |
131 | 6.19k | appendInt(value: Int64(value.rawValue)) |
132 | 60.0k | } |
133 | 60.0k | } |
134 | | |
135 | 265k | mutating func putFloatValue(value: Float) { |
136 | 265k | if value.isNaN { |
137 | 6.92k | append(staticText: "nan") |
138 | 265k | } else if !value.isFinite { |
139 | 98.2k | if value < 0 { |
140 | 33.6k | append(staticText: "-inf") |
141 | 98.2k | } else { |
142 | 64.5k | append(staticText: "inf") |
143 | 98.2k | } |
144 | 265k | } else { |
145 | 167k | data.append(contentsOf: value.debugDescription.utf8) |
146 | 265k | } |
147 | 265k | } |
148 | | |
149 | 966k | mutating func putDoubleValue(value: Double) { |
150 | 966k | if value.isNaN { |
151 | 3.76k | append(staticText: "nan") |
152 | 966k | } else if !value.isFinite { |
153 | 755k | if value < 0 { |
154 | 1.27k | append(staticText: "-inf") |
155 | 755k | } else { |
156 | 753k | append(staticText: "inf") |
157 | 755k | } |
158 | 966k | } else { |
159 | 210k | data.append(contentsOf: value.debugDescription.utf8) |
160 | 966k | } |
161 | 966k | } |
162 | | |
163 | 25.1M | private mutating func appendUInt(value: UInt64) { |
164 | 25.1M | if value >= 1000 { |
165 | 294k | appendUInt(value: value / 1000) |
166 | 25.1M | } |
167 | 25.1M | if value >= 100 { |
168 | 3.58M | data.append(asciiZero + UInt8((value / 100) % 10)) |
169 | 25.1M | } |
170 | 25.1M | if value >= 10 { |
171 | 10.5M | data.append(asciiZero + UInt8((value / 10) % 10)) |
172 | 25.1M | } |
173 | 25.1M | data.append(asciiZero + UInt8(value % 10)) |
174 | 25.1M | } |
175 | 2.61M | private mutating func appendInt(value: Int64) { |
176 | 2.61M | if value < 0 { |
177 | 649k | data.append(asciiMinus) |
178 | 649k | // This is the twos-complement negation of value, |
179 | 649k | // computed in a way that won't overflow a 64-bit |
180 | 649k | // signed integer. |
181 | 649k | appendUInt(value: 1 + ~UInt64(bitPattern: value)) |
182 | 2.61M | } else { |
183 | 1.96M | appendUInt(value: UInt64(bitPattern: value)) |
184 | 2.61M | } |
185 | 2.61M | } |
186 | | |
187 | 2.40M | mutating func putInt64(value: Int64) { |
188 | 2.40M | appendInt(value: value) |
189 | 2.40M | } |
190 | | |
191 | 23.9M | mutating func putUInt64(value: UInt64) { |
192 | 23.9M | appendUInt(value: value) |
193 | 23.9M | } |
194 | | |
195 | 10.6M | mutating func appendUIntHex(value: UInt64, digits: Int) { |
196 | 10.6M | if digits == 0 { |
197 | 906k | append(staticText: "0x") |
198 | 10.6M | } else { |
199 | 9.76M | appendUIntHex(value: value >> 4, digits: digits - 1) |
200 | 9.76M | let d = UInt8(truncatingIfNeeded: value % 16) |
201 | 9.76M | data.append(d < 10 ? asciiZero + d : asciiUpperA + d - 10) |
202 | 10.6M | } |
203 | 10.6M | } |
204 | | |
205 | 906k | mutating func putUInt64Hex(value: UInt64, digits: Int) { |
206 | 906k | appendUIntHex(value: value, digits: digits) |
207 | 906k | } |
208 | | |
209 | 159k | mutating func putBoolValue(value: Bool) { |
210 | 159k | append(staticText: value ? "true" : "false") |
211 | 159k | } |
212 | | |
213 | 150k | mutating func putStringValue(value: String) { |
214 | 150k | data.append(asciiDoubleQuote) |
215 | 21.6M | for c in value.unicodeScalars { |
216 | 21.6M | switch c.value { |
217 | 21.6M | // Special two-byte escapes |
218 | 21.6M | case 8: |
219 | 74.3k | append(staticText: "\\b") |
220 | 21.6M | case 9: |
221 | 26.7k | append(staticText: "\\t") |
222 | 21.6M | case 10: |
223 | 22.4k | append(staticText: "\\n") |
224 | 21.6M | case 11: |
225 | 78.9k | append(staticText: "\\v") |
226 | 21.6M | case 12: |
227 | 229k | append(staticText: "\\f") |
228 | 21.6M | case 13: |
229 | 7.24k | append(staticText: "\\r") |
230 | 21.6M | case 34: |
231 | 4.55k | append(staticText: "\\\"") |
232 | 21.6M | case 92: |
233 | 2.64k | append(staticText: "\\\\") |
234 | 21.6M | case 0...31, 127: // Octal form for C0 control chars |
235 | 6.42M | data.append(asciiBackslash) |
236 | 6.42M | data.append(asciiZero + UInt8(c.value / 64)) |
237 | 6.42M | data.append(asciiZero + UInt8(c.value / 8 % 8)) |
238 | 6.42M | data.append(asciiZero + UInt8(c.value % 8)) |
239 | 21.6M | case 0...127: // ASCII |
240 | 9.90M | data.append(UInt8(truncatingIfNeeded: c.value)) |
241 | 21.6M | case 0x80...0x7ff: |
242 | 12.0k | data.append(0xc0 + UInt8(c.value / 64)) |
243 | 12.0k | data.append(0x80 + UInt8(c.value % 64)) |
244 | 21.6M | case 0x800...0xffff: |
245 | 4.91M | data.append(0xe0 + UInt8(truncatingIfNeeded: c.value >> 12)) |
246 | 4.91M | data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f)) |
247 | 4.91M | data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f)) |
248 | 21.6M | default: |
249 | 4.81k | data.append(0xf0 + UInt8(truncatingIfNeeded: c.value >> 18)) |
250 | 4.81k | data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 12) & 0x3f)) |
251 | 4.81k | data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f)) |
252 | 4.81k | data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f)) |
253 | 21.6M | } |
254 | 21.6M | } |
255 | 150k | data.append(asciiDoubleQuote) |
256 | 150k | } |
257 | | |
258 | 225k | mutating func putBytesValue(value: Data) { |
259 | 225k | data.append(asciiDoubleQuote) |
260 | 256k | value.withUnsafeBytes { (body: UnsafeRawBufferPointer) in |
261 | 256k | if let p = body.baseAddress, body.count > 0 { |
262 | 80.1M | for i in 0..<body.count { |
263 | 80.1M | let c = p[i] |
264 | 80.1M | switch c { |
265 | 80.1M | // Special two-byte escapes |
266 | 80.1M | case 8: |
267 | 615k | append(staticText: "\\b") |
268 | 80.1M | case 9: |
269 | 62.1k | append(staticText: "\\t") |
270 | 80.1M | case 10: |
271 | 122k | append(staticText: "\\n") |
272 | 80.1M | case 11: |
273 | 25.8k | append(staticText: "\\v") |
274 | 80.1M | case 12: |
275 | 8.02k | append(staticText: "\\f") |
276 | 80.1M | case 13: |
277 | 5.37k | append(staticText: "\\r") |
278 | 80.1M | case 34: |
279 | 9.64k | append(staticText: "\\\"") |
280 | 80.1M | case 92: |
281 | 1.87k | append(staticText: "\\\\") |
282 | 80.1M | case 32...126: // printable ASCII |
283 | 11.4M | data.append(c) |
284 | 80.1M | default: // Octal form for non-printable chars |
285 | 67.9M | data.append(asciiBackslash) |
286 | 67.9M | data.append(asciiZero + UInt8(c / 64)) |
287 | 67.9M | data.append(asciiZero + UInt8(c / 8 % 8)) |
288 | 67.9M | data.append(asciiZero + UInt8(c % 8)) |
289 | 80.1M | } |
290 | 80.1M | } |
291 | 256k | } |
292 | 256k | } |
293 | 225k | data.append(asciiDoubleQuote) |
294 | 225k | } |
295 | | } |