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 } } }