-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStringProtocol+INCITS_4_1986.swift
More file actions
276 lines (265 loc) · 9.95 KB
/
Copy pathStringProtocol+INCITS_4_1986.swift
File metadata and controls
276 lines (265 loc) · 9.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//
// StringProtocol+INCITS_4_1986.swift
// swift-incits-4-1986
//
// Created by Coen ten Thije Boonkkamp on 22/11/2025.
//
public import ASCII_Primitives
public import Binary_Primitives
extension StringProtocol {
public typealias ASCII = INCITS_4_1986.ASCII<Self>
/// Access to ASCII type-level constants and methods
public static var ascii: ASCII.Type {
ASCII.self
}
/// Access to ASCII instance methods for this string
///
/// Provides instance-level access to ASCII validation and transformation methods.
/// Returns a generic `INCITS_4_1986.ASCII` wrapper that works directly with the
/// string without copying.
///
/// ## Usage
///
/// ```swift
/// "hello".ascii.isAllASCII // true
/// "hello".ascii.uppercased() // "HELLO"
/// "HELLO🌍".ascii.lowercased() // "hello🌍"
/// ```
///
/// ## See Also
///
/// - ``INCITS_4_1986/ASCII``
@inlinable
public var ascii: ASCII {
INCITS_4_1986.ASCII(self)
}
}
extension StringProtocol {
// False-positive position: `<S: StringProtocol>` is a generic-parameter
// constraint — `Self` here would demand identity, not conformance
// (P1a sequence/comparison precedent).
// swiftlint:disable prefer_self_in_static_references
/// Normalizes ASCII line endings in string to the specified style
///
/// Convenience method that delegates to byte-level `normalized(_:to:)`.
///
/// Example:
/// ```swift
/// INCITS_4_1986.normalized("line1\nline2\r\nline3", to: .crlf)
/// // "line1\r\nline2\r\nline3"
/// ```
public static func normalized<S: StringProtocol>(
_ s: S,
to lineEnding: INCITS_4_1986.FormatEffectors.Line.Ending
) -> S {
return .init(
decoding: INCITS_4_1986.normalized([UInt8](s.utf8), to: lineEnding),
as: UTF8.self
)
}
// swiftlint:enable prefer_self_in_static_references
/// Normalizes ASCII line endings to the specified style
///
/// Converts all line endings to a consistent format. Recognizes and normalizes
/// all common ASCII line ending styles: LF (`\n`), CR (`\r`), and CRLF (`\r\n`).
///
/// ## Usage
///
/// ```swift
/// // Works with String
/// "line1\nline2\r\n".normalized(to: .crlf)
///
/// // Works with Substring
/// "Hello\r\nWorld"[...].normalized(to: .lf)
/// ```
///
/// - Parameters:
/// - lineEnding: Target line ending style (`.lf`, `.cr`, or `.crlf`)
/// - encoding: Unicode encoding to use (defaults to UTF-8)
/// - Returns: New string with all line endings normalized to the specified style
///
/// ## See Also
///
/// - ``INCITS_4_1986/normalized(_:to:as:)``
public func normalized(
to lineEnding: INCITS_4_1986.FormatEffectors.Line.Ending
) -> Self {
Self.normalized(self, to: lineEnding)
}
}
extension StringProtocol {
/// Creates some StringProtocol from a line ending constant
///
/// Transforms a line ending enumeration value into its corresponding
/// string representation. This is useful when you need the actual line ending characters
/// as a string rather than as byte arrays.
///
/// ## Line Ending Values
///
/// - **`.lf`**: Returns `"\n"` (Line Feed, 0x0A)
/// - **`.cr`**: Returns `"\r"` (Carriage Return, 0x0D)
/// - **`.crlf`**: Returns `"\r\n"` (Carriage Return + Line Feed, 0x0D 0x0A)
///
/// ## Usage
///
/// ```swift
/// // Get line ending strings
/// let unix = String(ascii: .lf) // "\n"
/// let mac = String(ascii: .cr) // "\r"
/// let windows = String(ascii: .crlf) // "\r\n"
///
/// // Use in string concatenation
/// let line1 = "First line"
/// let line2 = "Second line"
/// let text = line1 + String(ascii: .crlf) + line2
/// // "First line\r\nSecond line"
///
/// // Build multi-line text with consistent endings
/// let lines = ["Header", "Content", "Footer"]
/// let document = lines.joined(separator: String(ascii: .crlf))
/// ```
///
/// - Parameter ascii: The line ending style to convert to some StringProtocol
/// - Returns: String containing the line ending character(s)
///
/// ## See Also
///
/// - ``LineEnding``
/// - ``INCITS_4_1986/crlf``
/// - ``normalized(to:as:)``
public init(ascii lineEnding: INCITS_4_1986.FormatEffectors.Line.Ending) {
// Use `[ASCII.Code](ascii: lineEnding)` (the typed init defined in
// swift-incits-4-1986). Bridge ASCII.Code → UInt8 via per-element
// `.underlying` lazy map so we call stdlib's
// `String.init(decoding: Sequence<UInt8>, as: UTF8.self)` directly
// without needing the BSLI byte-domain decoding overload.
// Fully-qualified `ASCII_Primitives.ASCII.Code` because `Self.ASCII`
// (`INCITS_4_1986.ASCII<Self>`) shadows the unqualified `ASCII`
// namespace in a `StringProtocol` extension.
let codes = [ASCII_Primitives.ASCII.Code](ascii: lineEnding)
self.init(decoding: codes.lazy.map(\.underlying), as: UTF8.self)
}
}
extension StringProtocol {
/// Creates a string from ASCII bytes with validation
///
/// Constructs a String from a byte array, returning `nil` if any byte is outside the valid
/// US-ASCII range (0x00-0x7F). This method ensures that only valid 7-bit ASCII data is
/// converted to a string.
///
/// ## Validation
///
/// The method validates that all bytes fall within the ASCII range before decoding.
/// Any byte with the high bit set (>= 0x80) will cause validation to fail and return `nil`.
///
/// ## Performance
///
/// This method performs O(n) validation before string construction. For known-valid ASCII data,
/// use ``String/ascii/unchecked(_:)`` to skip validation and improve performance.
///
/// ## Usage
///
/// ```swift
/// // Valid ASCII bytes
/// let hello = String(ascii: [104, 101, 108, 108, 111]) // "hello"
///
/// // Using INCITS constants
/// let bytes: [UInt8] = [
/// INCITS_4_1986.Character.Graphic.H,
/// INCITS_4_1986.Character.Graphic.i
/// ]
/// let text = String(ascii: bytes) // "Hi"
///
/// // Invalid ASCII bytes
/// String(ascii: [255]) // nil (0xFF is not valid 7-bit ASCII)
/// String(ascii: [0x80]) // nil (high bit set)
/// ```
///
/// - Parameter ascii: Array of bytes to validate and decode as ASCII
/// - Returns: String if all bytes are valid ASCII (0x00-0x7F), `nil` otherwise
///
/// ## See Also
///
/// - ``String/ascii/unchecked(_:)``
/// - ``INCITS_4_1986``
public init?(ascii bytes: [Byte]) {
// Validate each byte is in the 7-bit ASCII range (0x00–0x7F).
// [Byte] is the byte-domain substrate; bytes may be non-ASCII,
// so this validation is the load-bearing fallibility. Callers
// with [ASCII.Code] (whose type-level invariant already
// guarantees 0x00–0x7F) should reach for the non-failable
// ``init(ascii:)-[ASCII.Code]`` overload below.
guard bytes.allSatisfy({ $0.underlying < 0x80 }) else { return nil }
self.init(decoding: bytes.lazy.map(\.underlying), as: UTF8.self)
}
/// Creates a string from a sequence of `ASCII.Code` values.
///
/// Non-failable — `ASCII.Code` carries its 7-bit ASCII range as a
/// type-system invariant, so no per-element validation is needed.
/// Sibling of the fallible ``init(ascii:)-[Byte]`` overload; consumers
/// holding `[ASCII.Code]` (e.g., from a successful `try [ASCII.Code](bytes)`
/// lift or from named-constant literals like `[.H, .e, .l, .l, .o]`)
/// should reach for this overload directly without the explicit
/// `[Byte](codes)` bridge.
///
/// ## Usage
///
/// ```swift
/// let codes: [ASCII.Code] = [.H, .e, .l, .l, .o]
/// let s = String(ascii: codes) // "Hello"
/// ```
@inlinable
public init<Codes: Sequence>(ascii codes: Codes)
where Codes.Element == ASCII_Primitives.ASCII.Code {
self.init(decoding: codes.lazy.map(\.underlying), as: UTF8.self)
}
/// Creates a single-character string from an ASCII byte with validation
///
/// Returns `nil` if the byte is outside the valid ASCII range (0x00-0x7F).
///
/// ## Usage
///
/// ```swift
/// String(ascii: 0x41) // "A"
/// String(ascii: 0x20) // " "
/// String(ascii: 0xFF) // nil (not ASCII)
/// ```
///
/// - Parameter byte: The byte to validate and decode as ASCII
/// - Returns: Single-character string if byte is valid ASCII, `nil` otherwise
public init?(ascii byte: Byte) {
// Sibling of the `[Byte]` overload above; mirror its `< 0x80` ASCII
// guard (rather than the throwing `ASCII.Code(_: Byte)` init) and
// bridge to the stdlib UTF-8 decoder via `.underlying`.
guard byte.underlying < 0x80 else { return nil }
self.init(decoding: CollectionOfOne(byte.underlying), as: UTF8.self)
}
}
extension StringProtocol {
/// String representation of an ASCII-serializable value
///
/// Composes through canonical byte representation for academic correctness.
///
/// ## Category Theory
///
/// String display composes as:
/// ```
/// Serializable → [UInt8] (ASCII) → String (UTF-8 interpretation)
/// ```
///
/// ## Example
///
/// ```swift
/// let value: RFC_5322.EmailAddress = ...
/// let string = String(value) // Uses this initializer
/// ```
///
/// - Parameter value: Any type conforming to Binary.ASCII.Serializable
@_transparent
public init<T: Binary.Serializable>(_ value: T) {
// String(decoding:as:) stays UInt8 (stdlib idiom); cross the byte-domain
// boundary via the BSLI Sequence.underlying: [UInt8] accessor.
let typed: [Byte] = value.bytes
self = Self(decoding: typed.underlying, as: UTF8.self)
}
}