Swift implementation of INCITS 4-1986 (R2022): Coded Character Sets - 7-Bit American Standard Code for Information Interchange (US-ASCII).
This package provides a complete implementation of the US-ASCII character set standard. The implementation follows INCITS 4-1986 (Reaffirmed 2022), offering character classification, case conversion, string validation, and byte-level operations for all 128 ASCII characters (0x00-0x7F).
Pure Swift implementation with no Foundation dependencies, suitable for Swift Embedded and constrained environments.
- 128 ASCII character constants (0x00-0x7F) organized by standard sections
- Character classification predicates (whitespace, digits, letters, alphanumeric, hex digits)
- ASCII-only case conversion for strings and byte arrays
- String trimming with optimized UTF-8 fast path for ASCII whitespace
- Line ending normalization (LF, CR, CRLF) for cross-platform text processing
- Bidirectional String ⟷
[UInt8]conversion with ASCII validation - Cross-module inlining via
@inlinableand@_transparentfor zero-cost abstractions - 500+ tests covering edge cases, performance, and standards compliance
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/swift-standards/swift-incits-4-1986.git", from: "0.1.0")
]Then add the dependency to your target:
.target(
name: "YourTarget",
dependencies: [
.product(name: "INCITS 4 1986", package: "swift-incits-4-1986")
]
)import INCITS_4_1986
// Character classification
let byte: UInt8 = 0x41 // 'A'
byte.ascii.isLetter // true
byte.ascii.isUppercase // true
// Case conversion
"Hello World".ascii(case: .upper) // "HELLO WORLD"
// String ⟷ bytes conversion
let bytes = [UInt8].ascii("hello") // [104, 101, 108, 108, 111]
let text = String.ascii(bytes) // "hello"
// String trimming
" Hello ".trimming(.ascii.whitespaces) // "Hello"Direct access to all 128 ASCII characters:
import INCITS_4_1986
// Control characters (0x00-0x1F, 0x7F)
UInt8.ascii.nul // 0x00 (NULL)
UInt8.ascii.htab // 0x09 (HORIZONTAL TAB)
UInt8.ascii.lf // 0x0A (LINE FEED)
UInt8.ascii.cr // 0x0D (CARRIAGE RETURN)
UInt8.ascii.esc // 0x1B (ESCAPE)
UInt8.ascii.del // 0x7F (DELETE)
// SPACE (0x20) - dual nature character
UInt8.ascii.sp // 0x20 (SPACE)
// Graphic characters (0x21-0x7E)
UInt8.ascii.exclamationPoint // 0x21 (!)
UInt8.ascii.`0` // 0x30 (digit 0)
UInt8.ascii.`A` // 0x41 (capital A)
UInt8.ascii.`a` // 0x61 (lowercase a)
UInt8.ascii.tilde // 0x7E (~)
// Common sequences
let crlf = INCITS_4_1986.crlf // [0x0D, 0x0A]
let whitespaces = INCITS_4_1986.whitespaces // {0x20, 0x09, 0x0A, 0x0D}Test character properties at the byte level:
let byte: UInt8 = 0x41 // 'A'
// Character type classification
byte.ascii.isWhitespace // false
byte.ascii.isControl // false
byte.ascii.isPrintable // true
byte.ascii.isVisible // true
// Letter and digit classification
byte.ascii.isDigit // false
byte.ascii.isLetter // true
byte.ascii.isAlphanumeric // true
byte.ascii.isHexDigit // true (A-F are hex)
// Case classification
byte.ascii.isUppercase // true
byte.ascii.isLowercase // false
// Whitespace examples
UInt8.ascii.sp.ascii.isWhitespace // true (SPACE)
UInt8.ascii.htab.ascii.isWhitespace // true (TAB)
UInt8.ascii.lf.ascii.isWhitespace // true (LF)
UInt8.ascii.cr.ascii.isWhitespace // true (CR)Convert individual bytes between cases:
// Uppercase to lowercase
let upper: UInt8 = 0x41 // 'A'
let lower = upper.ascii(case: .lower) // 0x61 ('a')
// Lowercase to uppercase
let a: UInt8 = 0x61 // 'a'
let A = a.ascii(case: .upper) // 0x41 ('A')
// Non-letters unchanged
let digit: UInt8 = 0x30 // '0'
digit.ascii(case: .upper) // 0x30 (unchanged)Extract numeric values from ASCII digit bytes:
// Decimal digits
let five: UInt8 = 0x35 // '5'
UInt8.ascii(digit: five) // 5
// Hexadecimal digits
UInt8.ascii(hexDigit: 0x41) // 10 ('A')
UInt8.ascii(hexDigit: 0x61) // 10 ('a')
UInt8.ascii(hexDigit: 0x46) // 15 ('F')
UInt8.ascii(hexDigit: 0x66) // 15 ('f')Convert strings to ASCII byte arrays with validation:
// Valid ASCII string
let bytes = [UInt8].ascii("Hello")
// [72, 101, 108, 108, 111]
// With validation
if let asciiBytes = [UInt8].ascii("Hello World") {
// All characters are valid ASCII
}
// Non-ASCII returns nil
[UInt8].ascii("Hello🌍") // nil (emoji not ASCII)
[UInt8].ascii("café") // nil (é not ASCII)
// Unchecked conversion (no validation)
let bytes = [UInt8].ascii(unchecked: "Hello")
// Use only when you know string is ASCIIConvert ASCII byte arrays to strings:
// With validation
let text = String.ascii([72, 101, 108, 108, 111]) // "Hello"
// Invalid ASCII returns nil
String.ascii([0xFF]) // nil (not valid 7-bit ASCII)
String.ascii([0x80]) // nil (high bit set)
// Unchecked conversion (no validation)
let text = String.ascii(unchecked: [72, 105]) // "Hi"
// Use only when you know bytes are ASCIICheck if all bytes in an array are valid ASCII:
let hello: [UInt8] = [72, 101, 108, 108, 111]
hello.ascii.isAllASCII // true
let mixed: [UInt8] = [72, 101, 0xFF, 108, 111]
mixed.ascii.isAllASCII // false (0xFF > 0x7F)
let empty: [UInt8] = []
empty.ascii.isAllASCII // true (empty array is valid)Convert all ASCII letters in a byte array:
let hello = [UInt8].ascii("Hello World")!
// To uppercase
hello.ascii.convertingCase(to: .upper)
// [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68] ("HELLO WORLD")
// To lowercase
hello.ascii.convertingCase(to: .lower)
// [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100] ("hello world")
// Non-letters unchanged
let mixed = [UInt8].ascii("Test123!")!
mixed.ascii.convertingCase(to: .upper)
// [84, 69, 83, 84, 49, 50, 51, 33] ("TEST123!")Convert ASCII letters in strings (Unicode-safe):
// Basic case conversion
"Hello World".ascii(case: .upper) // "HELLO WORLD"
"Hello World".ascii(case: .lower) // "hello world"
// Unicode safety - non-ASCII preserved
"hello🌍".ascii(case: .upper) // "HELLO🌍"
"café".ascii(case: .upper) // "CAFé" (only ASCII 'c', 'a', 'f' converted)
// Only ASCII letters affected
"Test123!".ascii(case: .upper) // "TEST123!"Remove characters from both ends of strings:
// Trim ASCII whitespace
let text = " Hello World "
text.trimming(.ascii.whitespaces) // "Hello World"
// Static method
String.trimming(text, of: .ascii.whitespaces) // "Hello World"
// Trim custom characters
let padded = "***important***"
padded.trimming(["*"]) // "important"
let quoted = "\"hello\""
quoted.trimming(["\""]) // "hello"
// Only edges are trimmed
let spaced = " Hello World "
spaced.trimming(.ascii.whitespaces) // "Hello World"Normalize line endings for cross-platform compatibility:
// Normalize to CRLF (Windows, Internet protocols)
let text = "line1\nline2\rline3\r\nline4"
text.normalized(to: .crlf)
// "line1\r\nline2\r\nline3\r\nline4"
// Normalize to LF (Unix, Linux, macOS)
let windows = "Hello\r\nWorld\r\n"
windows.normalized(to: .lf) // "Hello\nWorld\n"
// Normalize to CR (Classic Mac OS)
let unix = "Hello\nWorld\n"
unix.normalized(to: .cr) // "Hello\rWorld\r"
// Handles mixed line endings
let mixed = "A\nB\rC\r\nD"
mixed.normalized(to: .lf) // "A\nB\nC\nD"Get line ending bytes for network protocols:
// String line endings
let lf = String.ascii(lineEnding: .lf) // "\n"
let cr = String.ascii(lineEnding: .cr) // "\r"
let crlf = String.ascii(lineEnding: .crlf) // "\r\n"
// Byte array line endings
let lfBytes = [UInt8].ascii(lineEnding: .lf) // [0x0A]
let crBytes = [UInt8].ascii(lineEnding: .cr) // [0x0D]
let crlfBytes = [UInt8].ascii(lineEnding: .crlf) // [0x0D, 0x0A]
// Use in protocol implementation
var httpResponse: [UInt8] = [UInt8].ascii("HTTP/1.1 200 OK")!
httpResponse += [UInt8].ascii.crlf
httpResponse += [UInt8].ascii("Content-Type: text/plain")!
httpResponse += [UInt8].ascii.crlfSwift Character extensions for ASCII operations:
let char: Character = "A"
// Classification
char.ascii.isWhitespace // false
char.ascii.isDigit // false
char.ascii.isLetter // true
char.ascii.isAlphanumeric // true
char.ascii.isHexDigit // true
char.ascii.isUppercase // true
char.ascii.isLowercase // false
// Character set operations
let whitespace: Set<Character> = .ascii.whitespaces // {' ', '\t', '\n', '\r'}
whitespace.contains(" ") // true
whitespace.contains("\t") // trueDirect access to the INCITS 4-1986 namespace:
// Access character constants
let space = INCITS_4_1986.SPACE.sp // 0x20
let tab = INCITS_4_1986.ControlCharacters.htab // 0x09
let letterA = INCITS_4_1986.GraphicCharacters.A // 0x41
// Common byte sequences
let crlf = INCITS_4_1986.crlf // [0x0D, 0x0A]
// Whitespace set
let whitespaces = INCITS_4_1986.whitespaces
// {0x20, 0x09, 0x0A, 0x0D}
// Case conversion offset
let offset = INCITS_4_1986.caseConversionOffset // 0x20
// 'A' (0x41) + 0x20 = 'a' (0x61)Benchmarked on Apple Silicon (M-series):
- ASCII validation: 17.3 MB/sec (1M bytes in ~58ms)
- Case conversion: 2.5 MB/sec (1M bytes in ~400ms)
- String normalization: 2 MB/sec (1M bytes in ~500ms)
- String trimming: 5 MB/sec (1M spaces in ~200ms)
All operations scale linearly with input size:
- 1K bytes → 10K bytes: 10× time increase
- 10K bytes → 100K bytes: 10× time increase
- 100K bytes → 1M bytes: 10× time increase
- Zero-copy UTF-8 fast path for ASCII operations
- Direct byte-level comparisons instead of Set lookups
- Cross-module inlining for zero-cost abstractions
- Minimal memory allocations (often zero for hot paths)
Conforms to INCITS 4-1986 (Reaffirmed 2022):
- Character set: 7-bit ASCII (0x00-0x7F, 128 characters)
- Control characters (Section 4.1): 33 characters (0x00-0x1F, 0x7F)
- SPACE (Section 4.2): Dual-nature character (0x20)
- Graphic characters (Section 4.3): 94 printable characters (0x21-0x7E)
- Current: INCITS 4-1986 (R2022)
- Previous: ANSI X3.4-1986
- Original: ANSI X3.4-1968, ASA X3.4-1963
- IANA: US-ASCII
- Whitespace: SPACE (0x20), HORIZONTAL TAB (0x09), LINE FEED (0x0A), CARRIAGE RETURN (0x0D)
- CRLF: Required line ending for HTTP, SMTP, FTP, MIME per their RFCs
- Case conversion: Offset of 0x20 between uppercase and lowercase letters
Test suite: 500+ tests in 80+ suites
Coverage:
- Character classification for all 128 ASCII bytes
- Case conversion (upper, lower, round-trip)
- String ⟷ byte array conversion (valid, invalid, empty)
- Line ending normalization (LF, CR, CRLF, mixed)
- String trimming (whitespace, custom sets)
- Edge cases (empty arrays, control characters, non-ASCII)
- Performance benchmarks at multiple scales
- Linear scaling validation
Run tests:
swift testRun specific test suites:
swift test --filter "Character Classification"
swift test --filter "Linear Scaling"- Swift 6.0 or later
- macOS 15.0+ / iOS 18.0+ / tvOS 18.0+ / watchOS 11.0+
- No Foundation dependencies (Swift Embedded compatible)
- swift-standards - Foundation utilities for standards implementations
This package is licensed under the Apache License 2.0. See LICENSE.md for details.
Contributions are welcome. Please ensure all tests pass and new features include test coverage.