|
| 1 | +@frozen |
| 2 | +public struct OSLogIntegerFormatting: Equatable { |
| 3 | + /// The base to use for the string representation. `radix` must be at least 2 |
| 4 | + /// and at most 36. The default is 10. |
| 5 | + public var radix: Int |
| 6 | + |
| 7 | + /// When set, a `+` will be printed for all non-negative integers. |
| 8 | + public var explicitPositiveSign: Bool |
| 9 | + |
| 10 | + /// When set, a prefix: 0b or 0o or 0x will be added when the radix is 2, 8 or |
| 11 | + /// 16 respectively. |
| 12 | + public var includePrefix: Bool |
| 13 | + |
| 14 | + /// Whether to use uppercase letters to represent numerals |
| 15 | + /// greater than 9 (default is to use lowercase). |
| 16 | + public var uppercase: Bool |
| 17 | + |
| 18 | + /// Minimum number of digits to display. Numbers having fewer digits than |
| 19 | + /// minDigits will be displayed with leading zeros. |
| 20 | + public var minDigits: Int |
| 21 | + |
| 22 | + /// - Parameters: |
| 23 | + /// - radix: The base to use for the string representation. `radix` must be |
| 24 | + /// at least 2 and at most 36. The default is 10. |
| 25 | + /// - explicitPositiveSign: Pass `true` to add a + sign to non-negative |
| 26 | + /// numbers. Default is `false`. |
| 27 | + /// - includePrefix: Pass `true` to add a prefix: 0b, 0o, 0x to corresponding |
| 28 | + /// radices. Default is `false`. |
| 29 | + /// - uppercase: Pass `true` to use uppercase letters to represent numerals |
| 30 | + /// greater than 9, or `false` to use lowercase letters. The default is |
| 31 | + /// `false`. |
| 32 | + /// - minDigits: minimum number of digits to display. Numbers will be |
| 33 | + /// prefixed with zeros if necessary to meet the minimum. The default is 1. |
| 34 | + @_semantics("constant_evaluable") |
| 35 | + @inlinable |
| 36 | + @_optimize(none) |
| 37 | + public init( |
| 38 | + radix: Int = 10, |
| 39 | + explicitPositiveSign: Bool = false, |
| 40 | + includePrefix: Bool = false, |
| 41 | + uppercase: Bool = false, |
| 42 | + minDigits: Int = 1 |
| 43 | + ) { |
| 44 | + precondition(radix >= 2 && radix <= 36) |
| 45 | + |
| 46 | + self.radix = radix |
| 47 | + self.explicitPositiveSign = explicitPositiveSign |
| 48 | + self.includePrefix = includePrefix |
| 49 | + self.uppercase = uppercase |
| 50 | + self.minDigits = minDigits |
| 51 | + } |
| 52 | + |
| 53 | + /// - Parameters: |
| 54 | + /// - explicitPositiveSign: Pass `true` to add a + sign to non-negative |
| 55 | + /// numbers. Default is `false`. |
| 56 | + /// - minDigits: minimum number of digits to display. Numbers will be |
| 57 | + /// prefixed with zeros if necessary to meet the minimum. The default is 1. |
| 58 | + @_semantics("constant_evaluable") |
| 59 | + @inlinable |
| 60 | + @_optimize(none) |
| 61 | + public static func decimal( |
| 62 | + explicitPositiveSign: Bool = false, |
| 63 | + minDigits: Int = 1 |
| 64 | + ) -> OSLogIntegerFormatting { |
| 65 | + return OSLogIntegerFormatting( |
| 66 | + radix: 10, |
| 67 | + explicitPositiveSign: explicitPositiveSign, |
| 68 | + minDigits: minDigits) |
| 69 | + } |
| 70 | + |
| 71 | + /// Default decimal format. |
| 72 | + @_semantics("constant_evaluable") |
| 73 | + @inlinable |
| 74 | + @_optimize(none) |
| 75 | + public static var decimal: OSLogIntegerFormatting { .decimal() } |
| 76 | + |
| 77 | + /// - Parameters: |
| 78 | + /// - explicitPositiveSign: Pass `true` to add a + sign to non-negative |
| 79 | + /// numbers. Default is `false`. |
| 80 | + /// - includePrefix: Pass `true` to add a prefix: 0b, 0o, 0x to corresponding |
| 81 | + /// radices. Default is `false`. |
| 82 | + /// - uppercase: Pass `true` to use uppercase letters to represent numerals |
| 83 | + /// greater than 9, or `false` to use lowercase letters. The default is |
| 84 | + /// `false`. |
| 85 | + /// - minDigits: minimum number of digits to display. Numbers will be |
| 86 | + /// prefixed with zeros if necessary to meet the minimum. The default is 1. |
| 87 | + @_semantics("constant_evaluable") |
| 88 | + @inlinable |
| 89 | + @_optimize(none) |
| 90 | + public static func hex( |
| 91 | + explicitPositiveSign: Bool = false, |
| 92 | + includePrefix: Bool = false, |
| 93 | + uppercase: Bool = false, |
| 94 | + minDigits: Int = 1 |
| 95 | + ) -> OSLogIntegerFormatting { |
| 96 | + return OSLogIntegerFormatting( |
| 97 | + radix: 16, |
| 98 | + explicitPositiveSign: explicitPositiveSign, |
| 99 | + includePrefix: includePrefix, |
| 100 | + uppercase: uppercase, |
| 101 | + minDigits: minDigits) |
| 102 | + } |
| 103 | + |
| 104 | + /// Default hexadecimal format. |
| 105 | + @_semantics("constant_evaluable") |
| 106 | + @inlinable |
| 107 | + @_optimize(none) |
| 108 | + public static var hex: OSLogIntegerFormatting { .hex() } |
| 109 | + |
| 110 | + /// - Parameters: |
| 111 | + /// - explicitPositiveSign: Pass `true` to add a + sign to non-negative |
| 112 | + /// numbers. Default is `false`. |
| 113 | + /// - includePrefix: Pass `true` to add a prefix: 0b, 0o, 0x to corresponding |
| 114 | + /// radices. Default is `false`. |
| 115 | + /// - uppercase: Pass `true` to use uppercase letters to represent numerals |
| 116 | + /// greater than 9, or `false` to use lowercase letters. The default is |
| 117 | + /// `false`. |
| 118 | + /// - minDigits: minimum number of digits to display. Numbers will be |
| 119 | + /// prefixed with zeros if necessary to meet the minimum. The default is 1. |
| 120 | + @_semantics("constant_evaluable") |
| 121 | + @inlinable |
| 122 | + @_optimize(none) |
| 123 | + public static func octal( |
| 124 | + explicitPositiveSign: Bool = false, |
| 125 | + includePrefix: Bool = false, |
| 126 | + uppercase: Bool = false, |
| 127 | + minDigits: Int = 1 |
| 128 | + ) -> OSLogIntegerFormatting { |
| 129 | + OSLogIntegerFormatting( |
| 130 | + radix: 8, |
| 131 | + explicitPositiveSign: explicitPositiveSign, |
| 132 | + includePrefix: includePrefix, |
| 133 | + uppercase: uppercase, |
| 134 | + minDigits: minDigits) |
| 135 | + } |
| 136 | + |
| 137 | + /// Default octal format. |
| 138 | + @_semantics("constant_evaluable") |
| 139 | + @inlinable |
| 140 | + @_optimize(none) |
| 141 | + public static var octal: OSLogIntegerFormatting { .octal() } |
| 142 | +} |
| 143 | + |
| 144 | +extension OSLogIntegerFormatting { |
| 145 | + /// The prefix for the radix in the Swift literal syntax. |
| 146 | + @_semantics("constant_evaluable") |
| 147 | + @inlinable |
| 148 | + @_optimize(none) |
| 149 | + internal var _prefix: String { |
| 150 | + guard includePrefix else { return "" } |
| 151 | + switch radix { |
| 152 | + case 2: return "0b" |
| 153 | + case 8: return "0o" |
| 154 | + case 16: return "0x" |
| 155 | + default: return "" |
| 156 | + } |
| 157 | + } |
| 158 | +} |
| 159 | + |
| 160 | +extension OSLogIntegerFormatting { |
| 161 | + |
| 162 | + /// Returns a fprintf-compatible length modifier for a given argument type. |
| 163 | + @_semantics("constant_evaluable") |
| 164 | + @inlinable |
| 165 | + @_optimize(none) |
| 166 | + internal static func formatSpecifierLengthModifier<I: FixedWidthInteger>( |
| 167 | + _ type: I.Type |
| 168 | + ) -> String? { |
| 169 | + // IEEE Std 1003.1-2017, length modifiers: |
| 170 | + switch type { |
| 171 | + // hh - d, i, o, u, x, or X conversion specifier applies to |
| 172 | + // (signed|unsigned) char |
| 173 | + case is CChar.Type: return "hh" |
| 174 | + case is CUnsignedChar.Type: return "hh" |
| 175 | + |
| 176 | + // h - d, i, o, u, x, or X conversion specifier applies to |
| 177 | + // (signed|unsigned) short |
| 178 | + case is CShort.Type: return "h" |
| 179 | + case is CUnsignedShort.Type: return "h" |
| 180 | + |
| 181 | + case is CInt.Type: return "" |
| 182 | + case is CUnsignedInt.Type: return "" |
| 183 | + |
| 184 | + // l - d, i, o, u, x, or X conversion specifier applies to |
| 185 | + // (signed|unsigned) long |
| 186 | + case is CLong.Type: return "l" |
| 187 | + case is CUnsignedLong.Type: return "l" |
| 188 | + |
| 189 | + // ll - d, i, o, u, x, or X conversion specifier applies to |
| 190 | + // (signed|unsigned) long long |
| 191 | + case is CLongLong.Type: return "ll" |
| 192 | + case is CUnsignedLongLong.Type: return "ll" |
| 193 | + |
| 194 | + default: return nil |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + /// Constructs an os_log format specifier for the given type `type` |
| 199 | + /// using the specified alignment `align` and privacy qualifier `privacy`. |
| 200 | + @_semantics("constant_evaluable") |
| 201 | + @inlinable |
| 202 | + @_optimize(none) |
| 203 | + @_effects(readonly) |
| 204 | + internal func formatSpecifier<I: FixedWidthInteger>( |
| 205 | + for type: I.Type, |
| 206 | + align: OSLogStringAlignment, |
| 207 | + privacy: OSLogPrivacy |
| 208 | + ) -> String { |
| 209 | + // Based on IEEE Std 1003.1-2017 |
| 210 | + // `d`/`i` is the only signed integral conversions allowed |
| 211 | + if (type.isSigned && radix != 10) { |
| 212 | + fatalError("Signed integers must be formatted using .decimal") |
| 213 | + } |
| 214 | + |
| 215 | + // IEEE: Each conversion specification is introduced by the '%' character |
| 216 | + // after which the following appear in sequence: |
| 217 | + // 1. Zero or more flags (in any order), which modify the meaning of the |
| 218 | + // conversion specification. |
| 219 | + // 2. An optional minimum field width (for alignment). If the converted |
| 220 | + // value has fewer bytes than the field width, it shall be padded with |
| 221 | + // <space> characters by default on the left; it shall be padded on the |
| 222 | + // right if the left-adjustment flag ( '-' ), is given to the |
| 223 | + // field width. |
| 224 | + // 3. An optional precision that gives the minimum number of digits to |
| 225 | + // display for the d, i, o, u, x, and X conversion specifiers. |
| 226 | + // 4. An optional length modifier that specifies the size of the argument. |
| 227 | + // 5. A conversion specifier character that indicates the type of |
| 228 | + // conversion to be applied. |
| 229 | + |
| 230 | + // Use Swift style prefixes rather than fprintf style prefixes. |
| 231 | + var specification = _prefix |
| 232 | + specification += "%" |
| 233 | + |
| 234 | + // Add privacy qualifier after % sign within curly braces. This is an |
| 235 | + // os log specific flag. |
| 236 | + switch privacy { |
| 237 | + case .private: |
| 238 | + specification += "{private}" |
| 239 | + case .public: |
| 240 | + specification += "{public}" |
| 241 | + default: |
| 242 | + break |
| 243 | + } |
| 244 | + |
| 245 | + // |
| 246 | + // 1. Flags |
| 247 | + // |
| 248 | + // Use `+` flag if signed, otherwise prefix a literal `+` for unsigned. |
| 249 | + if explicitPositiveSign { |
| 250 | + // IEEE: `+` The result of a signed conversion shall always begin with a |
| 251 | + // sign ( '+' or '-' ) |
| 252 | + if type.isSigned { |
| 253 | + specification += "+" |
| 254 | + } else { |
| 255 | + var newSpecification = "+" |
| 256 | + newSpecification += specification |
| 257 | + specification = newSpecification |
| 258 | + } |
| 259 | + } |
| 260 | + |
| 261 | + // IEEE: `-` The result of the conversion shall be left-justified within |
| 262 | + // the field. The conversion is right-justified if this flag is not |
| 263 | + // specified. |
| 264 | + switch align.anchor { |
| 265 | + case OSLogCollectionBound.start: |
| 266 | + specification += "-" |
| 267 | + default: |
| 268 | + break |
| 269 | + } |
| 270 | + |
| 271 | + // 2. Minimumn field width |
| 272 | + // IEEE: When field width is prefixed by `0`, leading zeros (following any |
| 273 | + // indication of sign or base) are used to pad to the field width rather |
| 274 | + // than performing space padding. If the '0' and '-' flags both appear, |
| 275 | + // the '0' flag is ignored. If a precision is specified, the '0' flag shall |
| 276 | + // be ignored. |
| 277 | + // |
| 278 | + // Commentary: `0` is prefixed to field width when the user doesn't want to |
| 279 | + // use precision (minDigits). This allows sign and prefix characters to be |
| 280 | + // counted towards field width (they wouldn't be counted towards precision). |
| 281 | + // This is more useful for floats, where precision is digits after the radix. |
| 282 | + // In our case, we're already handling prefix ourselves; we choose not to |
| 283 | + // support this functionality. In our case, alignment always pads spaces ( |
| 284 | + // to the left or right) until the minimum field width is met. |
| 285 | + if align.minimumColumnWidth > 0 { |
| 286 | + specification += align.minimumColumnWidth.description |
| 287 | + } |
| 288 | + |
| 289 | + // 3. Precision |
| 290 | + |
| 291 | + // Default precision for integers is 1, otherwise use the requested precision. |
| 292 | + if minDigits != 1 { |
| 293 | + specification += "." |
| 294 | + specification += minDigits.description |
| 295 | + } |
| 296 | + |
| 297 | + // 4. Length modifier |
| 298 | + guard let lengthModifier = |
| 299 | + OSLogIntegerFormatting.formatSpecifierLengthModifier(type) else { |
| 300 | + fatalError("Integer type has unknown byte length") |
| 301 | + } |
| 302 | + specification += lengthModifier |
| 303 | + |
| 304 | + // 5. The conversion specifier |
| 305 | + switch radix { |
| 306 | + case 10: |
| 307 | + specification += type.isSigned ? "d" : "u" |
| 308 | + case 8: |
| 309 | + specification += "o" |
| 310 | + case 16: |
| 311 | + specification += uppercase ? "X" : "x" |
| 312 | + default: |
| 313 | + fatalError("radix must be 10, 8 or 16") |
| 314 | + } |
| 315 | + return specification |
| 316 | + } |
| 317 | +} |
0 commit comments