swift-yaml

1.0.0

A pure Swift YAML parser with no external dependencies
1amageek/swift-yaml

What's New

1.0.0

2026-03-11T06:47:55Z

1.0.0 — Stable Release

Pure Swift YAML 1.2.2 parser with no external dependencies.

Features

  • ScalarStyle preservation.plain, .singleQuoted, .doubleQuoted, .literal, .folded
  • Tag support — Tags stored on Scalar, Mapping, and Sequence nodes
  • Multi-document parsingcomposeAll(yaml:) for YAML streams with ---/... markers
  • Depth limitingmaxDepth parameter (default 512) prevents stack overflow on malicious input
  • Custom Equatable/Hashable — Only content participates; mark, style, and tag are excluded

Public API

  • compose(yaml:maxDepth:)Node?
  • composeAll(yaml:maxDepth:)[Node]
  • Node (.scalar, .mapping, .sequence)
  • Node.Scalarstring, mark, style, tag
  • Node.Mapping — ordered pairs, subscript by string key, mark, tag
  • Node.Sequence — ordered nodes, mark, tag
  • ScalarStyle, Mark, YAMLError

Stats

  • 607 tests across 29 suites
  • Full YAML 1.2.2 spec compliance (Chapters 2–10)

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 nil

Supported YAML Features

Feature Example
Block mapping key: value
Block sequence - item
Flow sequence [a, b, c]
Flow mapping {key: value}
Literal block scalar |, |-, |+
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)
  1. Scanner — Tokenizes YAML input with indentation tracking, flow/block context switching, and block scalar processing
  2. Parser — Recursive descent parser that consumes tokens and builds the Node tree with anchor resolution
  3. 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.

Description

  • Swift Tools 6.2.0
View More Packages from this Author

Dependencies

  • None
Last updated: Wed Apr 08 2026 19:18:43 GMT-0900 (Hawaii-Aleutian Daylight Time)