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 namespace pattern for String, Substring, Character, UInt8, and
[UInt8]types - String and Substring character classification (isAllASCII, isAllDigits, isAllLetters, etc.)
- String and Substring case validation (isAllLowercase, isAllUppercase)
- String and Substring line ending detection and constants
- ASCII-only case conversion for strings, substrings, 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 - 513 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.ControlCharacters.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)
// Collection predicates (symmetric with StringProtocol)
let digits = [UInt8](ascii: "12345")!
digits.ascii.isAllDigits // true
digits.ascii.isAllLetters // false
let letters = [UInt8](ascii: "Hello")!
letters.ascii.isAllLetters // true
letters.ascii.isAllUppercase // false (mixed case)
letters.ascii.isAllLowercase // false (mixed case)
let upperOnly = [UInt8](ascii: "HELLO")!
upperOnly.ascii.isAllUppercase // trueConvert all ASCII letters in a byte array:
let hello = [UInt8](ascii: "Hello World")!
// Elegant case conversion
hello.ascii(case: .upper)
// [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68] ("HELLO WORLD")
hello.ascii(case: .lower)
// [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100] ("hello world")
// Non-letters unchanged
let mixed = [UInt8](ascii: "Test123!")!
mixed.ascii(case: .upper)
// [84, 69, 83, 84, 49, 50, 51, 33] ("TEST123!")
// Validation via callAsFunction
let valid: [UInt8] = [0x48, 0x69]
valid.ascii() // Optional([0x48, 0x69])
let invalid: [UInt8] = [0x48, 0xFF]
invalid.ascii() // nilConvert 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!"
// Convenience methods on .ascii namespace
"hello".ascii.uppercased() // "HELLO"
"WORLD".ascii.lowercased() // "world"Test string-level character properties:
let str = "Hello123"
// ASCII validation
str.ascii.isAllASCII // true
"Hello🌍".ascii.isAllASCII // false
// Content classification
"123456".ascii.isAllDigits // true
"abc".ascii.isAllLetters // true
"abc123".ascii.isAllAlphanumeric // true
" \t\n".ascii.isAllWhitespace // true
"\t\n\r".ascii.isAllControl // true
"ABC123!".ascii.isAllVisible // true
"hello world".ascii.isAllPrintable // true
// Contains checks
"hello".ascii.containsNonASCII // false
"café".ascii.containsNonASCII // true
"DEADBEEF".ascii.containsHexDigit // trueValidate case of ASCII letters in strings:
// Case validation (non-letters ignored)
"hello world".ascii.isAllLowercase // true
"HELLO WORLD".ascii.isAllUppercase // true
"Hello World".ascii.isAllLowercase // false
// Numbers and symbols don't affect validation
"hello123!".ascii.isAllLowercase // true
"HELLO123!".ascii.isAllUppercase // trueDetect and analyze line ending styles:
// Line ending constants
String.ascii.lf // "\n"
String.ascii.cr // "\r"
String.ascii.crlf // "\r\n"
// Detection
"A\nB\rC".ascii.containsMixedLineEndings // true (has both LF and CR)
"A\r\nB\r\n".ascii.containsMixedLineEndings // false (only CRLF)
// Detect line ending type
"Hello\nWorld".ascii.detectedLineEnding() // .lf
"Hello\r\nWorld".ascii.detectedLineEnding() // .crlf
"Hello\rWorld".ascii.detectedLineEnding() // .cr
"Hello World".ascii.detectedLineEnding() // nil (no line endings)All INCITS_4_1986.ASCII operations available on Substring:
let str = " Hello World "
let sub = str[...]
// Character classification
sub.ascii.isAllASCII
sub.ascii.isAllLetters
sub.ascii.containsNonASCII
// Case validation and conversion
sub.ascii.isAllLowercase
sub.ascii(case: .upper)
sub.ascii.uppercased()
sub.ascii.lowercased()
// Line ending detection
sub.ascii.containsMixedLineEndings
sub.ascii.detectedLineEnding()
// Trimming
sub.trimming(.ascii.whitespaces) // "Hello World"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: .lf) // "\n"
let cr = String(ascii: .cr) // "\r"
let crlf = String(ascii: .crlf) // "\r\n"
// Byte array line endings
let lfBytes = [UInt8](ascii: .lf) // [0x0A]
let crBytes = [UInt8](ascii: .cr) // [0x0D]
let crlfBytes = [UInt8](ascii: .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.ControlCharacters.crlf // [0x0D, 0x0A]
// Whitespace set
let whitespaces = INCITS_4_1986.whitespaces
// {0x20, 0x09, 0x0A, 0x0D}
// Case conversion offset
let offset = INCITS_4_1986.CaseConversion.offset // 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: 513 tests in 164 suites
Coverage:
- Character classification for all 128 ASCII bytes
- String and Substring character classification and validation
- String and Substring case validation and conversion
- String and Substring line ending detection
- 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.