feat(parsing): port libyaml input reader to Swift
Stage 3 foundation of the faithful libyaml scanner port. Translates libyaml's input buffer and position tracking (the buffer/mark fields and the PEEK/SKIP/SKIP_LINE macros) into PureYAML.Parsing.LibYAML.Reader. It iterates Unicode scalars rather than grapheme clusters so a CR and an LF are distinct, matching libyaml's byte-oriented scanning (a CR+LF pair is still one line break), and tracks the mark in libyaml's 0-based line and column convention. Unit-tested for peek/skip column tracking and LF and CRLF line breaks. This is the input layer the token fetchers consume.
This commit is contained in:
parent
348af8b77b
commit
7f94e17e2f
57
Sources/Parsing/LibYAML.Reader.swift
Normal file
57
Sources/Parsing/LibYAML.Reader.swift
Normal file
@ -0,0 +1,57 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Tests/LibYAMLReaderTests.swift
Normal file
46
Tests/LibYAMLReaderTests.swift
Normal file
@ -0,0 +1,46 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
/// Verifies the ported reader iterates Unicode scalars with libyaml's mark and
|
||||
/// `SKIP` / `SKIP_LINE` semantics: CR and LF are distinct scalars, a CR+LF pair
|
||||
/// is one line break, and the column is 0-based.
|
||||
@Suite("LibYAML Reader")
|
||||
struct LibYAMLReaderTests {
|
||||
@Test("peek and skip advance the column scalar by scalar")
|
||||
func test_peekAndSkip() {
|
||||
var reader = PureYAML.Parsing.LibYAML.Reader("ab")
|
||||
#expect(reader.peek() == "a")
|
||||
#expect(reader.peek(1) == "b")
|
||||
#expect(reader.peek(2) == nil)
|
||||
reader.skip()
|
||||
#expect(reader.peek() == "b")
|
||||
#expect(reader.column == 1)
|
||||
#expect(reader.line == 0)
|
||||
reader.skip()
|
||||
#expect(reader.isAtEnd)
|
||||
#expect(reader.peek() == nil)
|
||||
}
|
||||
|
||||
@Test("skipLine starts a new line and resets the column")
|
||||
func test_skipLineLF() {
|
||||
var reader = PureYAML.Parsing.LibYAML.Reader("a\nb")
|
||||
reader.skip()
|
||||
reader.skipLine()
|
||||
#expect(reader.line == 1)
|
||||
#expect(reader.column == 0)
|
||||
#expect(reader.peek() == "b")
|
||||
}
|
||||
|
||||
@Test("a CR+LF pair is a single line break")
|
||||
func test_skipLineCRLF() {
|
||||
var reader = PureYAML.Parsing.LibYAML.Reader("a\r\nb")
|
||||
reader.skip()
|
||||
// CR and LF are distinct scalars but one break.
|
||||
#expect(reader.peek() == "\r")
|
||||
#expect(reader.peek(1) == "\n")
|
||||
reader.skipLine()
|
||||
#expect(reader.line == 1)
|
||||
#expect(reader.column == 0)
|
||||
#expect(reader.peek() == "b")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user