PureYAML/docs/DESIGN.md

157 lines
5.9 KiB
Markdown

# PureYAML Design
| Field | Value |
|---|---|
| Status | draft |
| Created | 2026-06-05 |
| Last revised | 2026-06-05 |
## Purpose
PureYAML exists to provide a dependency-free YAML implementation in Swift that
can build on macOS, Linux, and WebAssembly/WASI. The first user is
OpenAPIDoctor, which needs YAML parsing and mutation without a C-backed parser
blocking browser deployment.
## Goals
- Parse a useful YAML subset with no external dependencies.
- Preserve mapping order.
- Emit YAML from the same model.
- Validate YAML structures with path-aware issues.
- Build the library target for `wasm32-unknown-wasip1`.
- Grow toward Yams-compatible behavior through tests, not source copying.
## Non-Goals
- No C source in the public package.
- No external SwiftPM dependency.
- No JavaScript parser or build-time generator.
- No claim of complete YAML 1.2 coverage until the test corpus proves it.
## Current Architecture
```text
PureYAML
├── Model
│ ├── Value
│ ├── Mapping
│ └── Pair
├── Parsing
│ ├── Parser
│ ├── Reader
│ ├── Scanner
│ ├── Token
│ ├── TokenCursor
│ ├── TokenEventParser
│ ├── EventComposer
│ ├── Event
│ ├── Mark
│ └── ParseError
├── Emitting
│ ├── Dumper
│ └── Options
├── Decoding
│ ├── Decoder
│ └── Error
├── Encoding
│ ├── Encoder
│ └── Error
└── Validation
├── Validator
├── Rule
├── Issue
└── Path
```
`PureYAML.parse(_:)` and `PureYAML.dump(_:)` are convenience entry points.
Parsing runs through the scanner, token-event parser, and event composer before
returning `PureYAML.Model.Value`. The implementation lives in namespaced parser,
dumper, scalar typed coder, and validator types.
## First Milestone
The initial parser supports:
- block mappings
- block sequences
- ordered mappings
- strings
- ints
- doubles
- bools
- nulls
- quoted strings
- comments outside quoted strings
- flow sequences and mappings
- literal and folded block scalars
- anchors and aliases
- YAML directives and document markers for single-document streams
- explicit built-in scalar tags for strings, integers, floats, booleans, and
nulls
The parser layer also has an internal event contract. `Parsing.Event` can
represent stream, document, scalar, sequence, mapping, and alias events, with
marks plus scalar and collection styles. Anchors and tags are carried as event
metadata so the later scanner/composer work can add YAML feature coverage
without changing the event shape.
The parser layer now also has an internal reader/scanner/event contract.
`Parsing.Reader` advances through Swift `String` input while tracking UTF-8 byte
indexes, lines, and columns. `Parsing.Scanner` emits lexical `Parsing.Token`
values for comments, indentation, block entries, mapping indicators, flow
delimiters, quoted scalar starts, block scalar headers, anchors, aliases, tags,
YAML directives, document markers, and source mark ranges. `Parsing.Scanner`
also expands tag handles from `%TAG` directives before emitting tag tokens.
`Parsing.TokenEventParser` consumes that token stream and emits `Parsing.Event`
values for block collections, flow collections, aliases, anchors, tags, and
scalars. `Parsing.EventComposer` consumes those events into
`PureYAML.Model.Value`, preserving ordered mapping pairs, block scalar text,
alias-resolved values, and explicit built-in scalar tags where the current model
can represent them.
The dumper emits deterministic YAML from the model. The default policy is
block-style collections with quoted strings. `Emitting.Options` can opt into
conservative plain scalars, safe literal block scalars for multiline strings,
and flow-style collections. Literal block scalar emission is intentionally
limited to strings whose lines round-trip through the current parser; other
multiline strings stay quoted. Flow collections always use inline scalars.
The validator traverses parsed YAML values and reports path-aware issues. The
default rule rejects duplicate mapping keys. Callers can compose custom rules on
top of the default validator or start from `Validation.Validator.blank` for
project-specific checks only. Strict validation treats warnings as failures;
non-strict validation returns warnings while still throwing for errors. The
validation corpus pins exact issue paths, descriptions, severity splits,
strict/non-strict behavior, rule traversal order, and duplicate-key diagnostics.
Typed conversion supports scalar single-value Decodable and Encodable values,
keyed mapping-backed structs with scalar, optional scalar, nested keyed struct,
and sequence fields, plus unkeyed sequences with scalar elements, optional
elements, nested sequences, and keyed mapping elements. `PureYAML.decode(_:from:)`,
`PureYAML.encode(_:)`, and `PureYAML.encodeToYAML(_:)` validate input shapes and
throw exact path-aware typed coding errors.
Typed decoding runs default validation before Swift `Decodable` construction.
That keeps ambiguous YAML states, such as duplicate mapping keys, from being
silently collapsed by keyed property lookup.
## Planned Compatibility Work
Compatibility should be added in small, test-backed slices:
1. Tag-aware collection handling and merge keys.
2. Additional built-in validation rules beyond duplicate-key behavior.
3. Broader Codable compatibility beyond scalar, keyed, and unkeyed containers.
4. Yams corpus comparison tests in a separate compatibility suite.
The private `PureYAMLResearch` repository may be used to study Yams behavior, but
the public implementation must be written in Swift and must not copy C parser
source.
## Attribution Boundary
Yams and its bundled `CYaml` / libyaml-derived C parser are compatibility
references for PureYAML. They should guide test cases and behavior questions.
They are not source material to copy into this package.