PureYAML/Sources/Parsing/Parser.ScalarComposition.swift
Mihaela Mihaljevic a8950e118c Merge conformance/yaml-test-suite-gaps: the libyaml port becomes the engine on main
The ported engine wins every conflict: main's post-fork patches to the
hand-rolled Scanner and TokenEventParser land on deleted files and are
dropped; their behaviors are carried by the port and guarded by the
whole-suite conformance gate (402/402 passing).

Merge adaptations, all gates green (style, namespacing, forbidden
patterns, changelog, roadmap, swiftformat, swiftlint strict, build,
full test suite, whole-suite conformance sweep):

- PureYAMLJSON representability rules move onto the Check algebra,
  replacing the retired legacy Rule type.
- The permissive tag-handle pre-pass is removed; the port rejects an
  undefined handle at parse time (suite case QLJ7) and the validation
  report carries it as a parse diagnostic.
- EventStreamComposer's nested types split one-per-file per the
  namespacing gate.
- Tests pinned to old-engine behavior were re-verified against the
  reference and updated: libyaml rejects an explicit-key continuation
  ending at a value colon, and suite case QB6E requires rejecting a
  quoted continuation at column 0.
- CHANGELOG unreleased entries from both lines are preserved in two
  groups with a provenance note.

Closes #1, closes #3
2026-07-05 00:23:48 +02:00

90 lines
3.0 KiB
Swift

extension PureYAML.Parsing.Parser {
func composeScalarValue(
_ value: String,
tag: String?,
style: PureYAML.Parsing.ScalarStyle,
mark: PureYAML.Parsing.Mark,
) throws -> PureYAML.Model.Value {
if let tagged = try composeTaggedScalarValue(value, tag: tag, mark: mark) {
return tagged
}
// An explicit tag that is not a recognized core type, a local tag like `!foo` or the
// non-specific `!`, still fixes the node as a string: a tagged scalar is not subject to the
// implicit plain-scalar resolution that would otherwise read `! 12` as the integer 12.
if normalizedTag(tag) != nil {
return .string(value)
}
switch style {
case .folded, .literal:
return .string(value)
case .plain:
return try parseScalar(value, line: mark.line)
case .doubleQuoted, .singleQuoted:
return .string(value)
}
}
func composeTaggedScalarValue(
_ value: String,
tag: String?,
mark: PureYAML.Parsing.Mark,
) throws -> PureYAML.Model.Value? {
guard let tag = normalizedTag(tag) else {
return nil
}
switch tag {
case "!":
// The non-specific `!` tag resolves a scalar by kind alone: a scalar is
// a string, with no implicit typing, so `! 12` is "12" not the int 12.
return .string(value)
case "tag:yaml.org,2002:str":
return .string(value)
case "tag:yaml.org,2002:int":
guard let int = parseInteger(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 = parseDouble(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 composeTaggedBoolValue(value, mark: mark)
case "tag:yaml.org,2002:null":
return .null
default:
return nil
}
}
func composeTaggedBoolValue(
_ value: String,
mark: PureYAML.Parsing.Mark,
) throws -> PureYAML.Model.Value {
guard let bool = parseBool(trim(value)) else {
throw invalidTaggedScalar(tag: "tag:yaml.org,2002:bool", value: value, mark: mark)
}
return .bool(bool)
}
func normalizedTag(_ tag: String?) -> String? {
PureYAML.Parsing.TagNormalizer.normalize(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,
)
}
}