PureYAML/Sources/Streaming/Streaming.Scanner.swift
Mihaela Mihaljevic 20f080a9f7 fix(streaming): replace the token-kind trap with an exhaustive switch
The 22-case TokenType mapping was split across three initializers to
stay under the complexity lint, leaving a preconditionFailure default
the types could not prove unreachable, which also tripped the
forbidden-patterns gate on every push. One exhaustive switch makes a
new TokenType case a compile error instead of a runtime trap; the lint
now ignores case statements when counting complexity, since exhaustive
closed-enum switches are the point, not a smell.
2026-07-12 13:26:06 +02:00

103 lines
4.4 KiB
Swift

public extension PureYAML.Streaming {
/// A resumable, chunked YAML token scanner: the public counterpart to the
/// internal libyaml-ported scanner's `scanStep()`, which this wraps
/// directly with no independent logic of its own. Proven to match batch
/// scanning byte-for-byte across the whole yaml-test-suite corpus and at
/// several chunk sizes (`StreamingScannerDifferentialTests` in the test
/// suite exercises the identical internal `scanStep()` this type calls).
struct Scanner: Sendable {
private var scanner: PureYAML.Parsing.LibYAML.Scanner
/// Creates a scanner with no input yet. Feed input with ``feed(_:)``
/// and mark the end of input with ``finish()``.
public init() {
scanner = PureYAML.Parsing.LibYAML.Scanner("", isFinal: false)
}
/// Appends a chunk of YAML source. Chunks are concatenated in the
/// order fed; a chunk need not align with any YAML grammar boundary.
public mutating func feed(_ chunk: String) {
scanner.reader.append(chunk)
}
/// Marks that no further input will be fed. After this, ``next()``
/// never returns ``Outcome/needMoreInput``: a fetch that would have
/// needed more input instead surfaces as a real scanning error, since
/// there is no more input coming that could resolve it.
public mutating func finish() {
scanner.reader.markFinal()
}
/// Advances the scan by one token. Call in a loop, feeding more input
/// on ``Outcome/needMoreInput`` and stopping on ``Outcome/done``.
public mutating func next() throws -> Outcome {
switch try scanner.scanStep() {
case let .token(token):
.token(Token(token))
case .needMoreInput:
.needMoreInput
case .done:
.done
}
}
}
}
extension PureYAML.Streaming.Token {
init(_ token: PureYAML.Parsing.LibYAML.Token) {
self.init(
kind: Kind(token.type),
range: PureYAML.Model.SourceRange(
start: PureYAML.Model.SourcePosition(line: token.startMark.line, column: token.startMark.column),
end: PureYAML.Model.SourcePosition(line: token.endMark.line, column: token.endMark.column),
),
)
}
}
extension PureYAML.Streaming.Token.Kind {
/// A mechanical 1:1 mapping over all 22 `TokenType` cases in one
/// exhaustive switch: no `default`, so adding a `TokenType` case is a
/// compile error here instead of a runtime trap. The lint's complexity
/// metric ignores case statements precisely for closed-enum mappings
/// like this one.
init(_ type: PureYAML.Parsing.LibYAML.TokenType) {
switch type {
case .streamStart: self = .streamStart
case .streamEnd: self = .streamEnd
case .documentStart: self = .documentStart
case .documentEnd: self = .documentEnd
case .blockSequenceStart: self = .blockSequenceStart
case .blockMappingStart: self = .blockMappingStart
case .blockEnd: self = .blockEnd
case .blockEntry: self = .blockEntry
case .flowSequenceStart: self = .flowSequenceStart
case .flowMappingStart: self = .flowMappingStart
case .flowSequenceEnd: self = .flowSequenceEnd
case .flowMappingEnd: self = .flowMappingEnd
case .flowEntry: self = .flowEntry
case .key: self = .key
case .value: self = .value
case .reservedDirective: self = .reservedDirective
case let .versionDirective(major, minor): self = .versionDirective(major: major, minor: minor)
case let .tagDirective(handle, prefix): self = .tagDirective(handle: handle, prefix: prefix)
case let .anchor(name): self = .anchor(name: name)
case let .alias(name): self = .alias(name: name)
case let .tag(handle, suffix): self = .tag(handle: handle, suffix: suffix)
case let .scalar(value, style): self = .scalar(value: value, style: PureYAML.Streaming.Token.ScalarStyle(style))
}
}
}
extension PureYAML.Streaming.Token.ScalarStyle {
init(_ style: PureYAML.Parsing.ScalarStyle) {
switch style {
case .plain: self = .plain
case .singleQuoted: self = .singleQuoted
case .doubleQuoted: self = .doubleQuoted
case .literal: self = .literal
case .folded: self = .folded
}
}
}