PureYAML/Sources/Parsing/EventComposer.swift

277 lines
8.3 KiB
Swift

extension PureYAML.Parsing {
struct EventComposer {
var events: [Event]
var index: Int
var anchors: [String: PureYAML.Model.Value]
let scalarParser: Parser
init(
events: [Event],
scalarParser: Parser,
) {
self.events = events
index = 0
anchors = [:]
self.scalarParser = scalarParser
}
mutating func compose() throws -> PureYAML.Model.Value {
_ = try expect("stream start") { event in
if case .streamStart = event {
return true
}
return false
}
_ = try expect("document start") { event in
if case .documentStart = event {
return true
}
return false
}
let value = try composeNode()
_ = try expect("document end") { event in
if case .documentEnd = event {
return true
}
return false
}
_ = try expect("stream end") { event in
if case .streamEnd = event {
return true
}
return false
}
return value
}
}
}
extension PureYAML.Parsing.EventComposer {
mutating func composeNode() throws -> PureYAML.Model.Value {
guard let event = current else {
throw unexpectedEvent(expected: "YAML node")
}
switch event {
case let .alias(anchor, mark):
index += 1
guard let value = anchors[anchor] else {
throw PureYAML.Parsing.ParseError.undefinedAlias(
anchor: anchor,
line: mark.line,
column: mark.column,
)
}
return value
case let .mappingStart(anchor, _, _, _):
let value = try composeMapping()
store(value, anchor: anchor)
return value
case let .scalar(value, anchor, tag, style, mark):
index += 1
let model = try composeScalar(
value,
tag: tag,
style: style,
mark: mark,
)
store(model, anchor: anchor)
return model
case let .sequenceStart(anchor, _, _, _):
let value = try composeSequence()
store(value, anchor: anchor)
return value
default:
throw unexpectedEvent(expected: "YAML node")
}
}
mutating func composeSequence() throws -> PureYAML.Model.Value {
_ = try expect("sequence start") { event in
if case .sequenceStart = event {
return true
}
return false
}
var values: [PureYAML.Model.Value] = []
while current?.isSequenceEnd != true {
try values.append(composeNode())
}
_ = try expect("sequence end") { $0.isSequenceEnd }
return .sequence(values)
}
mutating func composeMapping() throws -> PureYAML.Model.Value {
_ = try expect("mapping start") { event in
if case .mappingStart = event {
return true
}
return false
}
var pairs: [PureYAML.Model.Pair] = []
while current?.isMappingEnd != true {
let key = try composeMappingKey()
let value = try composeNode()
pairs.append(PureYAML.Model.Pair(key: key, value: value))
}
_ = try expect("mapping end") { $0.isMappingEnd }
return .mapping(PureYAML.Model.Mapping(pairs))
}
mutating func composeMappingKey() throws -> String {
guard let event = current else {
throw unexpectedEvent(expected: "mapping key")
}
guard case let .scalar(value, anchor, _, _, _) = event else {
let mark = event.mark
throw PureYAML.Parsing.ParseError.expectedScalarKey(line: mark.line, column: mark.column)
}
index += 1
store(.string(value), anchor: anchor)
return value
}
func composeScalar(
_ value: String,
tag: String?,
style: PureYAML.Parsing.ScalarStyle,
mark: PureYAML.Parsing.Mark,
) throws -> PureYAML.Model.Value {
if let tagged = try composeTaggedScalar(value, tag: tag, mark: mark) {
return tagged
}
switch style {
case .folded, .literal:
return .string(value)
case .plain:
return try scalarParser.parseScalar(value, line: mark.line)
case .doubleQuoted, .singleQuoted:
return .string(value)
}
}
func composeTaggedScalar(
_ value: String,
tag: String?,
mark: PureYAML.Parsing.Mark,
) throws -> PureYAML.Model.Value? {
guard let tag = normalizedScalarTag(tag) else {
return nil
}
switch tag {
case "tag:yaml.org,2002:str":
return .string(value)
case "tag:yaml.org,2002:int":
guard let int = Int(scalarParser.trim(value)) else {
throw invalidTaggedScalar(tag: "tag:yaml.org,2002:int", value: value, mark: mark)
}
return .int(int)
case "tag:yaml.org,2002:float":
guard let double = Double(scalarParser.trim(value)) else {
throw invalidTaggedScalar(tag: "tag:yaml.org,2002:float", value: value, mark: mark)
}
return .double(double)
case "tag:yaml.org,2002:bool":
return try composeTaggedBool(value, mark: mark)
case "tag:yaml.org,2002:null":
return .null
default:
return nil
}
}
func composeTaggedBool(
_ value: String,
mark: PureYAML.Parsing.Mark,
) throws -> PureYAML.Model.Value {
switch scalarParser.trim(value) {
case "true", "True", "TRUE":
return .bool(true)
case "false", "False", "FALSE":
return .bool(false)
default:
throw invalidTaggedScalar(tag: "tag:yaml.org,2002:bool", value: value, mark: mark)
}
}
func normalizedScalarTag(_ tag: String?) -> String? {
guard let tag else {
return nil
}
if tag.hasPrefix("!<"), tag.hasSuffix(">") {
return String(tag.dropFirst(2).dropLast())
}
switch tag {
case "!!str":
return "tag:yaml.org,2002:str"
case "!!int":
return "tag:yaml.org,2002:int"
case "!!float":
return "tag:yaml.org,2002:float"
case "!!bool":
return "tag:yaml.org,2002:bool"
case "!!null":
return "tag:yaml.org,2002:null"
default:
return tag
}
}
func invalidTaggedScalar(
tag: String,
value: String,
mark: PureYAML.Parsing.Mark,
) -> PureYAML.Parsing.ParseError {
PureYAML.Parsing.ParseError.invalidTaggedScalar(
tag: tag,
value: value,
line: mark.line,
column: mark.column,
)
}
}
extension PureYAML.Parsing.EventComposer {
var current: PureYAML.Parsing.Event? {
guard events.indices.contains(index) else {
return nil
}
return events[index]
}
mutating func store(
_ value: PureYAML.Model.Value,
anchor: String?,
) {
guard let anchor else {
return
}
anchors[anchor] = value
}
mutating func expect(
_ expected: String,
matching: (PureYAML.Parsing.Event) -> Bool,
) throws -> PureYAML.Parsing.Event {
guard let event = current else {
throw unexpectedEvent(expected: expected)
}
guard matching(event) else {
throw unexpectedEvent(expected: expected)
}
index += 1
return event
}
func unexpectedEvent(expected: String) -> PureYAML.Parsing.ParseError {
let mark = current?.mark ?? events.last?.mark ?? .start
return PureYAML.Parsing.ParseError.unexpectedEvent(
expected: expected,
actual: current?.description ?? "end of events",
line: mark.line,
column: mark.column,
)
}
}