extension PureYAML.Parsing.LibYAML { /// Port of libyaml's input buffer and position tracking (the `buffer`/`mark` /// fields with the `PEEK`, `SKIP`, and `SKIP_LINE` macros). It iterates /// `Unicode.Scalar`s rather than grapheme clusters so a CR and an LF are /// distinct, matching libyaml's byte-oriented scanning, and tracks the mark in /// libyaml's 0-based line and column convention; conversion to the package's /// 1-based `Mark` happens at token emission during integration. struct Reader { private let scalars: [Unicode.Scalar] private(set) var index = 0 private(set) var line = 0 private(set) var column = 0 init(_ string: String) { scalars = Array(string.unicodeScalars) } /// `PEEK_TOKEN` / `CHECK_AT`: the scalar `offset` ahead, or nil past the /// end (libyaml's `\0`). func peek(_ offset: Int = 0) -> Unicode.Scalar? { let position = index + offset return position < scalars.count ? scalars[position] : nil } var isAtEnd: Bool { index >= scalars.count } /// The current position. Column is 0-based, as in libyaml. var mark: PureYAML.Parsing.Mark { PureYAML.Parsing.Mark(line: line, column: column, index: index) } /// `SKIP`: consume one non-break scalar, advancing the column. mutating func skip() { guard index < scalars.count else { return } index += 1 column += 1 } /// `SKIP_LINE`: consume a line break and start a new line. A CR+LF pair is /// one break, matching libyaml. mutating func skipLine() { if peek() == "\r", peek(1) == "\n" { index += 2 } else if CharacterClass.isBreak(peek()) { index += 1 } else { return } line += 1 column = 0 } } }