PureYAML/Sources/Parsing/Scanner.State.swift
Mihaela M ba000d90fa Merge tag-handle diagnostics onto main
Per-document %TAG scoping and an undefinedTagHandle diagnostic via
validationReport, with Scanner.State and the tag helpers in their own files.
Resolved against main's Scanner.State by carrying its sawNonPropertyContentOnLine
field into the extracted State.
2026-06-29 09:14:21 +02:00

113 lines
6.8 KiB
Swift

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<Int> = []
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
}
}
}