PureYAML/Sources/Parsing/Parser.swift

139 lines
5.0 KiB
Swift

public extension PureYAML.Parsing {
/// Pure-Swift YAML parser for block mappings, sequences, and common scalars.
struct Parser: Sendable {
public init() {}
public func parse(_ yaml: String) throws -> PureYAML.Model.Value {
let lines = try preprocess(yaml)
guard !lines.isEmpty else {
throw ParseError.emptyDocument
}
var index = 0
let value = try parseBlock(lines, index: &index, indent: lines[0].indent)
if index < lines.count {
throw ParseError.unexpectedIndentation(line: lines[index].number)
}
return value
}
}
}
extension PureYAML.Parsing.Parser {
func parseBlock(
_ lines: [PureYAML.Parsing.Line],
index: inout Int,
indent: Int,
) throws -> PureYAML.Model.Value {
guard index < lines.count else {
return .null
}
let line = lines[index]
if line.indent < indent {
return .null
}
guard line.indent == indent else {
throw PureYAML.Parsing.ParseError.unexpectedIndentation(line: line.number)
}
if line.content.hasPrefix("-") {
return try parseSequence(lines, index: &index, indent: indent)
}
if splitMappingEntry(line.content) != nil {
return try parseMapping(lines, index: &index, indent: indent)
}
index += 1
return try parseScalar(line.content, line: line.number)
}
func parseSequence(
_ lines: [PureYAML.Parsing.Line],
index: inout Int,
indent: Int,
) throws -> PureYAML.Model.Value {
var values: [PureYAML.Model.Value] = []
while index < lines.count {
let line = lines[index]
if line.indent < indent {
break
}
guard line.indent == indent else {
throw PureYAML.Parsing.ParseError.unexpectedIndentation(line: line.number)
}
guard line.content == "-" || line.content.hasPrefix("- ") else {
throw PureYAML.Parsing.ParseError.mixedCollectionStyles(line: line.number)
}
let rest = trim(String(line.content.dropFirst()))
index += 1
if rest.isEmpty {
if index < lines.count, lines[index].indent > indent {
try values.append(parseBlock(lines, index: &index, indent: lines[index].indent))
} else {
values.append(.null)
}
} else if let (key, valueText) = splitMappingEntry(rest) {
var pairs = try [
PureYAML.Model.Pair(
key: key,
value: parseInlineOrNestedValue(valueText, lines: lines, index: &index, parentIndent: indent, line: line.number),
),
]
if index < lines.count, lines[index].indent > indent {
try pairs.append(contentsOf: parseMappingPairs(lines, index: &index, indent: lines[index].indent))
}
values.append(.mapping(PureYAML.Model.Mapping(pairs)))
} else {
try values.append(parseScalar(rest, line: line.number))
}
}
return .sequence(values)
}
func parseMapping(
_ lines: [PureYAML.Parsing.Line],
index: inout Int,
indent: Int,
) throws -> PureYAML.Model.Value {
try .mapping(PureYAML.Model.Mapping(parseMappingPairs(lines, index: &index, indent: indent)))
}
func parseMappingPairs(
_ lines: [PureYAML.Parsing.Line],
index: inout Int,
indent: Int,
) throws -> [PureYAML.Model.Pair] {
var pairs: [PureYAML.Model.Pair] = []
while index < lines.count {
let line = lines[index]
if line.indent < indent {
break
}
guard line.indent == indent else {
throw PureYAML.Parsing.ParseError.unexpectedIndentation(line: line.number)
}
guard let (key, valueText) = splitMappingEntry(line.content) else {
throw PureYAML.Parsing.ParseError.expectedMappingKey(line: line.number)
}
index += 1
let value = try parseInlineOrNestedValue(valueText, lines: lines, index: &index, parentIndent: indent, line: line.number)
pairs.append(PureYAML.Model.Pair(key: key, value: value))
}
return pairs
}
func parseInlineOrNestedValue(
_ valueText: String,
lines: [PureYAML.Parsing.Line],
index: inout Int,
parentIndent: Int,
line: Int,
) throws -> PureYAML.Model.Value {
if !valueText.isEmpty {
return try parseScalar(valueText, line: line)
}
if index < lines.count, lines[index].indent > parentIndent {
return try parseBlock(lines, index: &index, indent: lines[index].indent)
}
return .null
}
}