extension PureYAML.Parsing.Scanner { struct State { var reader: PureYAML.Parsing.Reader var tokens: [PureYAML.Parsing.Token] = [] var indentation: [Int] = [0] var isAtLineStart = true var hasDocumentContent = false var hasDocumentStartMarker = false var isDocumentClosed = false var hasYAMLDirective = false // The line of a directive awaiting its document. A directive must be followed by an explicit // `---`; this is set when one is scanned, cleared at a document start, and checked at a // document end and at stream end so a directive with no document is rejected. var pendingDirectiveLine: Int? var flowDepth = 0 var flowContexts: [PureYAML.Parsing.ScannerFlowContext] = [] var validImplicitIndentation: Set = [] var pendingBlockScalarIndentation = false var pendingBlockScalarIndentationIndicator: Int? /// Whether the pending block scalar is the document's own root node (no enclosing collection), /// so its content may begin at column 0 (the document parent's indentation being -1). var pendingBlockScalarIsRootNode = false /// Whether the pending block scalar is an inline mapping value (`- key: |2`), whose parent /// mapping sits at an implicit indentation off the main stack, so an explicit indentation /// indicator is measured relative to that level rather than the stack's. var pendingBlockScalarInlineMappingValue = false /// The leading-whitespace characters of each blank line between a block scalar header and its /// first content line. Stored verbatim (not just counted) so once the content indentation is /// known, any surplus past it, notably a tab on an otherwise-blank line, survives as content. var pendingBlockScalarLeadingBlankWhitespace: [String] = [] /// The widest indentation seen on a block scalar's leading blank lines (before its first /// content line). The spec forbids a leading empty line from being more indented than the /// content, so this is compared to the content indentation once that is known. var pendingBlockScalarMaxBlankIndent = 0 /// The count of leading space characters (before any tab) from the most recent indentation /// scan. Indentation is measured in spaces, so this, not the tab-expanded width, is what the /// leading-blank comparison uses: a tab is not indentation. var lastLeadingSpaceCount = 0 // The literal leading-whitespace characters from the most recent indentation scan. Block // scalar content keeps tabs beyond its (space-only) indentation verbatim, so the surplus is // reconstructed from these characters, not as spaces from the tab-expanded width. var lastLeadingWhitespace = "" var blockScalarIndentation: Int? var blockScalarContentPrefix = "" /// Whether a real token has been scanned on the current physical line. Reset when the scan /// loop consumes a line break and set as each token is scanned, so the scanner can tell a /// comment that opens a line (valid) from one glued to earlier content on it (invalid). A /// simple flag, not a token-line comparison, because a blank line inside a block scalar /// emits an empty scalar attributed to the next line, which would read as phantom content. var sawContentOnLine = false /// Like ``sawContentOnLine`` but not set by a node property (an anchor or tag). A `:` whose /// only preceding same-line content is a property (`&a :`, `!!str :`) is an empty implicit /// key carrying that property, so a null mapping key is synthesized for it exactly as for a /// bare `: value`. Reset at each line break. var sawNonPropertyContentOnLine = false // Whether a block-level mapping-value indicator (`:`) has been emitted on the current line. // Once one has, a later scalar followed by `:` is not a new implicit key: block style allows // only one mapping pair per line, so the document would be a forbidden inline nested mapping // (`a: b: c`). Reset at each line break. Flow `:` do not set it, since flow allows several // pairs on a line (`{a: b, c: d}`). var sawMappingValueOnLine = false /// Whether a flow collection has closed on the current line at the top level. A block /// sequence entry cannot follow a flow node on the same line (`{a: b}- x`), unlike a nested /// block entry (`- -`), which is not preceded by a flow close. Reset at each line break. var sawFlowCloseOnLine = false // Whether an explicit `?` mapping key is awaiting its `:` value indicator. A `:` that opens // a line is that key's value when one is pending, otherwise it is an implicit null key // (`: value`). Only the `?` indicator sets this; a synthesized implicit key does not. var pendingExplicitKey = false /// The tag handles in scope for the current document. A `%TAG` directive's handle applies /// only to the document it precedes (YAML 1.2), so handles are scoped per document, not /// accumulated across the stream: `pendingTagHandles` collects a document's directives and is /// merged onto the defaults at the document start, then both reset at the document boundary. static let defaultTagHandles = [ "!": "!", "!!": "tag:yaml.org,2002:", ] var tagHandles = Self.defaultTagHandles var pendingTagHandles: [String: String] = [:] /// Tag shorthands using a handle never declared in their document; collected, not rejected. var tagHandleIssues: [PureYAML.Parsing.TagHandleIssue] = [] mutating func append(_ kind: PureYAML.Parsing.TokenKind) { let mark = reader.mark tokens.append(PureYAML.Parsing.Token(kind: kind, mark: mark, endMark: mark)) markDocumentContentIfNeeded(kind) } mutating func append( _ kind: PureYAML.Parsing.TokenKind, mark: PureYAML.Parsing.Mark, endMark: PureYAML.Parsing.Mark, ) { tokens.append(PureYAML.Parsing.Token(kind: kind, mark: mark, endMark: endMark)) markDocumentContentIfNeeded(kind) } mutating func markDocumentContentIfNeeded(_ kind: PureYAML.Parsing.TokenKind) { switch kind { case .comment, .dedent, .documentEnd, .documentStart, .indent, .streamEnd, .streamStart: return default: hasDocumentContent = true } } var isExpectingFlowMappingKey: Bool { flowContexts.last?.kind == .mapping && flowContexts.last?.isExpectingKey == true } } }