Skip to content

Commit

Permalink
Implement Anchor coding, Tag coding and redundancy auto-aliasing
Browse files Browse the repository at this point in the history
  • Loading branch information
Adora Lynch committed Sep 20, 2024
1 parent 53d8b3b commit aa983d8
Show file tree
Hide file tree
Showing 22 changed files with 1,541 additions and 121 deletions.
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

##### Breaking

* None.

##### Enhancements

* Yams is able to encode and decode Anchors via YamlAnchorProviding, and
YamlAnchorCoding.
[Adora Lynch](https://github.com/lynchsft)
[#125](https://github.com/jpsim/Yams/issues/125)

* Yams is able to encode and decode Tags via YamlTagProviding
and YamlTagCoding.
[Adora Lynch](https://github.com/lynchsft)
[#265](https://github.com/jpsim/Yams/issues/265)

* Yams is able to detect redundant structes and automaticaly
alias them during encoding via RedundancyAliasingStrategy
[Adora Lynch](https://github.com/lynchsft)

##### Bug Fixes

* None.

## 5.2.0

##### Breaking

* Swift 5.7 or later is now required to build Yams.
[JP Simard](https://github.com/jpsim)

Expand Down
36 changes: 36 additions & 0 deletions Sources/Yams/Anchor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Anchor.swift
// Yams
//
// Created by Adora Lynch on 8/9/24.
// Copyright (c) 2024 Yams. All rights reserved.

import Foundation

public final class Anchor: RawRepresentable, ExpressibleByStringLiteral, Codable, Hashable {

public static let permittedCharacters = CharacterSet.lowercaseLetters
.union(.uppercaseLetters)
.union(.decimalDigits)
.union(.init(charactersIn: "-_"))

public static func is_cyamlAlpha(_ string: String) -> Bool {
Anchor.permittedCharacters.isSuperset(of: .init(charactersIn: string))
}


public let rawValue: String

public init(rawValue: String) {
self.rawValue = rawValue
}

public init(stringLiteral value: String) {
rawValue = value
}
}

extension Anchor: CustomStringConvertible {
public var description: String { rawValue }
}

2 changes: 2 additions & 0 deletions Sources/Yams/Constructor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public final class Constructor {
return result
}
return [Any].construct_seq(from: sequence)
case .alias(_):
preconditionFailure("Aliases should be resolved before construction")
}
}

Expand Down
51 changes: 48 additions & 3 deletions Sources/Yams/Decoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,17 @@ public class YAMLDecoder {
from yaml: String,
userInfo: [CodingUserInfoKey: Any] = [:]) throws -> T where T: Swift.Decodable {
do {
let node = try Parser(yaml: yaml, resolver: Resolver([.merge]), encoding: encoding).singleRoot() ?? ""
return try self.decode(type, from: node, userInfo: userInfo)
let parser = try Parser(yaml: yaml, resolver: Resolver([.merge]), encoding: encoding)
// ^ the parser holds the references to Anchors while parsing,
return try withExtendedLifetime(parser) {
//^ so we hold an explicit reference to the parser during decoding
let node = try parser.singleRoot() ?? ""
// ^ nodes only have weak references to Anchors (the Anchors would disappear if not held by the parser)
return try self.decode(type, from: node, userInfo: userInfo)
// ^ if the decoded type or contained types are YamlAnchorCoding,
// those types have taken ownership of Anchors.
// Otherwise the Anchors are deallocated when this function exits just like Tag and Mark
}
} catch let error as DecodingError {
throw error
} catch {
Expand Down Expand Up @@ -129,6 +138,8 @@ private struct _Decoder: Decoder {
throw _typeMismatch(at: codingPath, expectation: Node.Scalar.self, reality: mapping)
case .sequence(let sequence):
throw _typeMismatch(at: codingPath, expectation: Node.Scalar.self, reality: sequence)
case .alias(let alias):
throw _typeMismatch(at: codingPath, expectation: Node.Scalar.self, reality: alias)
}
}
}
Expand All @@ -140,7 +151,41 @@ private struct _KeyedDecodingContainer<Key: CodingKey>: KeyedDecodingContainerPr

init(decoder: _Decoder, wrapping mapping: Node.Mapping) {
self.decoder = decoder
self.mapping = mapping

let keys = mapping.keys

let decodeAnchor: Anchor?
let decodeTag: Tag?

if let anchor = mapping.anchor, keys.contains(.anchorKeyNode) == false {
decodeAnchor = anchor
} else {
decodeAnchor = nil
}

if mapping.tag.name != .implicit && keys.contains(.tagKeyNode) == false {
decodeTag = mapping.tag
} else {
decodeTag = nil
}

switch (decodeAnchor, decodeTag) {
case (nil, nil):
self.mapping = mapping
case (let anchor?, nil):
var mutableMapping = mapping
mutableMapping[.anchorKeyNode] = .scalar(.init(anchor.rawValue))
self.mapping = mutableMapping
case (nil, let tag?):
var mutableMapping = mapping
mutableMapping[.tagKeyNode] = .scalar(.init(tag.name.rawValue))
self.mapping = mutableMapping
case let (anchor?, tag?):
var mutableMapping = mapping
mutableMapping[.anchorKeyNode] = .scalar(.init(anchor.rawValue))
mutableMapping[.tagKeyNode] = .scalar(.init(tag.name.rawValue))
self.mapping = mutableMapping
}
}

// MARK: - Swift.KeyedDecodingContainerProtocol Methods
Expand Down
124 changes: 79 additions & 45 deletions Sources/Yams/Emitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ public func dump(
sortKeys: Bool = false,
sequenceStyle: Node.Sequence.Style = .any,
mappingStyle: Node.Mapping.Style = .any,
newLineScalarStyle: Node.Scalar.Style = .any) throws -> String {
newLineScalarStyle: Node.Scalar.Style = .any,
redundancyAliasingStrategy: RedundancyAliasingStrategy? = nil) throws -> String {

Check failure on line 103 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 14.3

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 103 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 15.0

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 103 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 14 with Xcode 15.4

cannot find type 'RedundancyAliasingStrategy' in scope
return try serialize(
node: object.represented(),
canonical: canonical,
Expand All @@ -113,7 +114,8 @@ public func dump(
sortKeys: sortKeys,
sequenceStyle: sequenceStyle,
mappingStyle: mappingStyle,
newLineScalarStyle: newLineScalarStyle
newLineScalarStyle: newLineScalarStyle,
redundancyAliasingStrategy: redundancyAliasingStrategy
)
}

Expand Down Expand Up @@ -148,7 +150,8 @@ public func serialize<Nodes>(
sortKeys: Bool = false,
sequenceStyle: Node.Sequence.Style = .any,
mappingStyle: Node.Mapping.Style = .any,
newLineScalarStyle: Node.Scalar.Style = .any) throws -> String
newLineScalarStyle: Node.Scalar.Style = .any,
redundancyAliasingStrategy: RedundancyAliasingStrategy? = nil) throws -> String

Check failure on line 154 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 14.3

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 154 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 15.0

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 154 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 14 with Xcode 15.4

cannot find type 'RedundancyAliasingStrategy' in scope
where Nodes: Sequence, Nodes.Iterator.Element == Node {
let emitter = Emitter(
canonical: canonical,
Expand All @@ -162,7 +165,8 @@ public func serialize<Nodes>(
sortKeys: sortKeys,
sequenceStyle: sequenceStyle,
mappingStyle: mappingStyle,
newLineScalarStyle: newLineScalarStyle
newLineScalarStyle: newLineScalarStyle,
redundancyAliasingStrategy: redundancyAliasingStrategy
)
try emitter.open()
try nodes.forEach(emitter.serialize)
Expand Down Expand Up @@ -201,7 +205,8 @@ public func serialize(
sortKeys: Bool = false,
sequenceStyle: Node.Sequence.Style = .any,
mappingStyle: Node.Mapping.Style = .any,
newLineScalarStyle: Node.Scalar.Style = .any) throws -> String {
newLineScalarStyle: Node.Scalar.Style = .any,
redundancyAliasingStrategy: RedundancyAliasingStrategy? = nil) throws -> String {

Check failure on line 209 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 14.3

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 209 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 15.0

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 209 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 14 with Xcode 15.4

cannot find type 'RedundancyAliasingStrategy' in scope
return try serialize(
nodes: [node],
canonical: canonical,
Expand All @@ -215,7 +220,8 @@ public func serialize(
sortKeys: sortKeys,
sequenceStyle: sequenceStyle,
mappingStyle: mappingStyle,
newLineScalarStyle: newLineScalarStyle
newLineScalarStyle: newLineScalarStyle,
redundancyAliasingStrategy: redundancyAliasingStrategy
)
}

Expand Down Expand Up @@ -246,10 +252,10 @@ public final class Emitter {
public var allowUnicode: Bool = false
/// Set the preferred line break.
public var lineBreak: LineBreak = .ln

// internal since we don't know if these should be exposed.
var explicitStart: Bool = false
var explicitEnd: Bool = false
/// Set to emit an explicit document start marker.
public var explicitStart: Bool = false
/// Set to emit an explicit document end marker.
public var explicitEnd: Bool = false

/// The `%YAML` directive value or nil.
public var version: (major: Int, minor: Int)?
Expand All @@ -265,6 +271,49 @@ public final class Emitter {

/// Set the style for scalars that include newlines
public var newLineScalarStyle: Node.Scalar.Style = .any

/// Redundancy aliasing strategy to use when encoding. Defaults to nil
public var redundancyAliasingStrategy: RedundancyAliasingStrategy?

Check failure on line 276 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 14.3

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 276 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 15.0

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 276 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 14 with Xcode 15.4

cannot find type 'RedundancyAliasingStrategy' in scope

/// Create `Emitter.Options` with the specified values.
///
/// - parameter canonical: Set if the output should be in the "canonical" format described in the YAML
/// specification.
/// - parameter indent: Set the indentation value.
/// - parameter width: Set the preferred line width. -1 means unlimited.
/// - parameter allowUnicode: Set if unescaped non-ASCII characters are allowed.
/// - parameter lineBreak: Set the preferred line break.
/// - parameter explicitStart: Explicit document start `---`.
/// - parameter explicitEnd: Explicit document end `...`.
/// - parameter version: The `%YAML` directive value or nil.
/// - parameter sortKeys: Set if emitter should sort keys in lexicographic order.
/// - parameter sequenceStyle: Set the style for sequences (arrays / lists)
/// - parameter mappingStyle: Set the style for mappings (dictionaries)
/// - parameter newLineScalarStyle: Set the style for newline-containing scalars
/// - parameter redundancyAliasingStrategy: Set the strategy for identifying redundant structures and automatically aliasing them
public init(canonical: Bool = false, indent: Int = 0, width: Int = 0, allowUnicode: Bool = false,
lineBreak: Emitter.LineBreak = .ln,
explicitStart: Bool = false,
explicitEnd: Bool = false,
version: (major: Int, minor: Int)? = nil,
sortKeys: Bool = false, sequenceStyle: Node.Sequence.Style = .any,
mappingStyle: Node.Mapping.Style = .any,
newLineScalarStyle: Node.Scalar.Style = .any,
redundancyAliasingStrategy: RedundancyAliasingStrategy? = nil) {

Check failure on line 302 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 14.3

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 302 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 15.0

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 302 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 14 with Xcode 15.4

cannot find type 'RedundancyAliasingStrategy' in scope
self.canonical = canonical
self.indent = indent
self.width = width
self.allowUnicode = allowUnicode
self.lineBreak = lineBreak
self.explicitStart = explicitStart
self.explicitEnd = explicitEnd
self.version = version
self.sortKeys = sortKeys
self.sequenceStyle = sequenceStyle
self.mappingStyle = mappingStyle
self.newLineScalarStyle = newLineScalarStyle
self.redundancyAliasingStrategy = redundancyAliasingStrategy
}
}

/// Configuration options to use when emitting YAML.
Expand All @@ -288,6 +337,8 @@ public final class Emitter {
/// - parameter sortKeys: Set if emitter should sort keys in lexicographic order.
/// - parameter sequenceStyle: Set the style for sequences (arrays / lists)
/// - parameter mappingStyle: Set the style for mappings (dictionaries)
/// - parameter newLineScalarStyle: Set the style for newline-containing scalars
/// - parameter redundancyAliasingStrategy: Set the strategy for identifying redundant structures and automatically aliasing them
public init(canonical: Bool = false,
indent: Int = 0,
width: Int = 0,
Expand All @@ -299,7 +350,8 @@ public final class Emitter {
sortKeys: Bool = false,
sequenceStyle: Node.Sequence.Style = .any,
mappingStyle: Node.Mapping.Style = .any,
newLineScalarStyle: Node.Scalar.Style = .any) {
newLineScalarStyle: Node.Scalar.Style = .any,
redundancyAliasingStrategy: RedundancyAliasingStrategy? = nil) {

Check failure on line 354 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 14.3

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 354 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 15.0

cannot find type 'RedundancyAliasingStrategy' in scope

Check failure on line 354 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 14 with Xcode 15.4

cannot find type 'RedundancyAliasingStrategy' in scope
options = Options(canonical: canonical,
indent: indent,
width: width,
Expand All @@ -311,7 +363,8 @@ public final class Emitter {
sortKeys: sortKeys,
sequenceStyle: sequenceStyle,
mappingStyle: mappingStyle,
newLineScalarStyle: newLineScalarStyle)
newLineScalarStyle: newLineScalarStyle,
redundancyAliasingStrategy: redundancyAliasingStrategy)
// configure emitter
yaml_emitter_initialize(&emitter)
yaml_emitter_set_output(&self.emitter, { pointer, buffer, size in
Expand Down Expand Up @@ -413,38 +466,10 @@ public final class Emitter {
}
}

// MARK: - Options Initializer
//// MARK: - Options Initializer

extension Emitter.Options {
/// Create `Emitter.Options` with the specified values.
///
/// - parameter canonical: Set if the output should be in the "canonical" format described in the YAML
/// specification.
/// - parameter indent: Set the indentation value.
/// - parameter width: Set the preferred line width. -1 means unlimited.
/// - parameter allowUnicode: Set if unescaped non-ASCII characters are allowed.
/// - parameter lineBreak: Set the preferred line break.
/// - parameter explicitStart: Explicit document start `---`.
/// - parameter explicitEnd: Explicit document end `...`.
/// - parameter version: The `%YAML` directive value or nil.
/// - parameter sortKeys: Set if emitter should sort keys in lexicographic order.
/// - parameter sequenceStyle: Set the style for sequences (arrays / lists)
/// - parameter mappingStyle: Set the style for mappings (dictionaries)
public init(canonical: Bool = false, indent: Int = 0, width: Int = 0, allowUnicode: Bool = false,
lineBreak: Emitter.LineBreak = .ln, version: (major: Int, minor: Int)? = nil,
sortKeys: Bool = false, sequenceStyle: Node.Sequence.Style = .any,
mappingStyle: Node.Mapping.Style = .any, newLineScalarStyle: Node.Scalar.Style = .any) {
self.canonical = canonical
self.indent = indent
self.width = width
self.allowUnicode = allowUnicode
self.lineBreak = lineBreak
self.version = version
self.sortKeys = sortKeys
self.sequenceStyle = sequenceStyle
self.mappingStyle = mappingStyle
self.newLineScalarStyle = newLineScalarStyle
}

}

// MARK: Implementation Details
Expand All @@ -461,8 +486,17 @@ extension Emitter {
case .scalar(let scalar): try serializeScalar(scalar)
case .sequence(let sequence): try serializeSequence(sequence)
case .mapping(let mapping): try serializeMapping(mapping)
case .alias(let alias): try serializeAlias(alias)
}
}

private func serializeAlias(_ alias: Node.Alias) throws {

Check failure on line 493 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 14.3

'Alias' is not a member type of enum 'Yams.Node'

Check failure on line 493 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 13 with Xcode 15.0

'Alias' is not a member type of enum 'Yams.Node'

Check failure on line 493 in Sources/Yams/Emitter.swift

View workflow job for this annotation

GitHub Actions / macOS 14 with Xcode 15.4

'Alias' is not a member type of enum 'Yams.Node'
var event = yaml_event_t()
let anchor = alias.anchor.rawValue
yaml_alias_event_initialize(&event,
anchor)
try emit(&event)
}

private func serializeScalar(_ scalar: Node.Scalar) throws {
var value = scalar.string.utf8CString, tag = scalar.resolvedTag.name.rawValue.utf8CString
Expand All @@ -472,7 +506,7 @@ extension Emitter {
tag.withUnsafeMutableBytes { tag in
yaml_scalar_event_initialize(
&event,
nil,
scalar.anchor?.rawValue,
tag.baseAddress?.assumingMemoryBound(to: UInt8.self),
value.baseAddress?.assumingMemoryBound(to: UInt8.self),
Int32(value.count - 1),
Expand All @@ -492,7 +526,7 @@ extension Emitter {
_ = tag.withUnsafeMutableBytes { tag in
yaml_sequence_start_event_initialize(
&event,
nil,
sequence.anchor?.rawValue,
tag.baseAddress?.assumingMemoryBound(to: UInt8.self),
implicit,
sequenceStyle)
Expand All @@ -511,7 +545,7 @@ extension Emitter {
_ = tag.withUnsafeMutableBytes { tag in
yaml_mapping_start_event_initialize(
&event,
nil,
mapping.anchor?.rawValue,
tag.baseAddress?.assumingMemoryBound(to: UInt8.self),
implicit,
mappingStyle)
Expand Down
Loading

0 comments on commit aa983d8

Please sign in to comment.