1amageek/swift-yaml
A pure Swift YAML 1.2.2 parser with zero dependencies. Parses YAML strings into a typed Node tree with full spec compliance, verified by 569 tests.
Installation
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/1amageek/swift-yaml.git", from: "0.3.0")
]Then add "YAML" to your target's dependencies:
.target(name: "YourTarget", dependencies: ["YAML"])Usage
import YAML
let node = try compose(yaml: """
server:
host: localhost
port: 8080
features: [auth, logging]
""")
if case .mapping(let root) = node,
case .mapping(let server) = root["server"] {
print(server["host"]) // Optional(.scalar("localhost"))
print(server["port"]) // Optional(.scalar("8080"))
print(server["features"]) // Optional(.sequence([...]))
}Node Types
The parser produces a recursive Node tree with three cases:
| Case | Type | Description | |---|---|---| | .scalar(Scalar) | Node.Scalar | String value with optional source position | | .mapping(Mapping) | Node.Mapping | Ordered key-value pairs (RandomAccessCollection) | | .sequence(Sequence) | Node.Sequence | Ordered list of nodes (RandomAccessCollection) |
Access values using computed properties:
node.scalar?.string // "value" or nil
node.mapping?["key"] // Node? or nil
node.sequence?[0] // Node or nilSupported YAML Features
| Feature | Example | |---|---| | Block mapping | key: value | | Block sequence | - item | | Flow sequence | [a, b, c] | | Flow mapping | {key: value} | | Literal block scalar | <code>|</code>, <code>|-</code>, <code>|+</code> | | Folded block scalar | >, >-, >+ | | Single-quoted string | 'it''s escaped' | | Double-quoted string | "hello\nworld" | | Anchors & aliases | &anchor / *anchor | | Tags | !!str, !custom, !<verbatim> | | Multi-document | --- / ... | | Directives | %YAML 1.2, %TAG | | Comments | # comment | | Complex mapping keys | {flow: map}: value, [seq]: value | | Escape sequences | All 18 YAML escapes + \xNN, \uNNNN, \UNNNNNNNN | | Nested structures | Arbitrary depth |
Error Handling
All parse errors are reported as YAMLError with source position:
do {
let node = try compose(yaml: invalidYAML)
} catch let error as YAMLError {
print(error) // "3:12: scanner error: unterminated double-quoted string"
}Type Safety
All public types conform to Sendable, Hashable, and Equatable:
// Use nodes as dictionary keys or set elements
var seen: Set<Node> = []
seen.insert(node)
// Compare nodes
if node == .scalar(Node.Scalar("expected")) { ... }Architecture
The parser uses a classic three-stage pipeline:
YAML String → Scanner → Parser → Node Tree
(tokens) (recursive descent)- Scanner — Tokenizes YAML input with indentation tracking, flow/block context switching, and block scalar processing
- Parser — Recursive descent parser that consumes tokens and builds the
Nodetree with anchor resolution - Compose — Public entry point (
compose(yaml:)) that wires Scanner and Parser together
API Reference
Entry Point
public func compose(yaml: String) throws -> Node?Returns nil for empty documents. Throws YAMLError on invalid input.
Node
public enum Node: Sendable, Hashable {
case scalar(Scalar)
case mapping(Mapping)
case sequence(Sequence)
var scalar: Scalar? { get }
var mapping: Mapping? { get }
var sequence: Sequence? { get }
}Node.Scalar
public struct Scalar: Sendable, Hashable {
public let string: String
public let mark: Mark?
public init(_ string: String, mark: Mark? = nil)
}Node.Mapping
public struct Mapping: Sendable, Hashable, RandomAccessCollection {
// Element = (key: Node, value: Node)
public subscript(key: String) -> Node? // lookup by string key
public subscript(position: Int) -> (Node, Node) // access by index
public var first: (key: Node, value: Node)?
public var isEmpty: Bool
}Node.Sequence
public struct Sequence: Sendable, Hashable, RandomAccessCollection {
// Element = Node
public subscript(position: Int) -> Node
}Mark
public struct Mark: Sendable, Hashable, CustomStringConvertible {
public let line: Int // 1-based
public let column: Int // 1-based
}YAMLError
public enum YAMLError: Error, Sendable, CustomStringConvertible {
case scanner(message: String, mark: Mark)
case parser(message: String, mark: Mark)
case unexpectedEndOfInput(mark: Mark)
}Requirements
- Swift 6.2+
License
MIT License. See LICENSE for details.
Package Metadata
Repository: 1amageek/swift-yaml
Default branch: main
README: README.md