Skip to content

Commit

Permalink
Merge pull request #428 from lynchsft/anchor_tag_and_autoaliasing
Browse files Browse the repository at this point in the history
Implement Anchor coding, Tag coding and redundancy auto-aliasing
  • Loading branch information
lynchsft authored Feb 17, 2025
2 parents db9ff23 + 630e59e commit 2688707
Show file tree
Hide file tree
Showing 26 changed files with 1,687 additions and 148 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pod_lib_lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ jobs:
- run: bundle install --path vendor/bundle
- if: matrix.platform == 'visionOS'
run: xcodebuild -downloadPlatform visionOS
- run: bundle exec pod lib lint --platforms=${{ matrix.platform }} --verbose
- run: bundle exec pod lib lint --platforms=${{ matrix.platform }} --verbose
30 changes: 20 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,37 @@

##### Enhancements

* Removes dependency on libc and the platform-specific pow function.
[Bradley Mackey](https://github.com/bradleymackey)
[#429](https://github.com/jpsim/Yams/issues/429)
* 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)

##### Bug Fixes
* 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)

* Yams will now correctly error when it tries to decode a mapping with duplicate keys.
[Tejas Sharma](https://github.com/tejassharma96)
[#415](https://github.com/jpsim/Yams/issues/415)
##### Bug Fixes

## 5.1.4
* None.

## 5.2.0

##### Breaking

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

##### Enhancements

* None.

* Removes dependency on libc and the platform-specific pow function.
[Bradley Mackey](https://github.com/bradleymackey)
[#429](https://github.com/jpsim/Yams/issues/429)

##### Bug Fixes

* Yams will now correctly error when it tries to decode a mapping with duplicate keys.
Expand Down
40 changes: 40 additions & 0 deletions Sources/Yams/Anchor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Anchor.swift
// Yams
//
// Created by Adora Lynch on 8/9/24.
// Copyright (c) 2024 Yams. All rights reserved.

import Foundation

/// A representation of a YAML tag see: https://yaml.org/spec/1.2.2/
/// Types interested in Encoding and Decoding Anchors should
/// conform to YamlAnchorProviding and YamlAnchorCoding respectively.
public final class Anchor: RawRepresentable, ExpressibleByStringLiteral, Codable, Hashable {

/// A CharacterSet containing only characters which are permitted by the underlying cyaml implementation
public static let permittedCharacters = CharacterSet.lowercaseLetters
.union(.uppercaseLetters)
.union(.decimalDigits)
.union(.init(charactersIn: "-_"))

/// Returns true if and only if `string` contains only characters which are also in `permittedCharacters`
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
}
}

/// Conformance of Anchor to CustomStringConvertible returns `rawValue` as `description`
extension Anchor: CustomStringConvertible {
public var description: String { rawValue }
}
7 changes: 6 additions & 1 deletion Sources/Yams/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@

add_library(Yams
Anchor.swift
Constructor.swift
Decoder.swift
Emitter.swift
Encoder.swift
Mark.swift
Node.Alias.swift
Node.Mapping.swift
Node.Scalar.swift
Node.Sequence.swift
Node.swift
Parser.swift
RedundancyAliasingStrategy.swift
Representer.swift
Resolver.swift
String+Yams.swift
Tag.swift
YamlError.swift)
YamlAnchorProviding.swift
YamlError.swift
YamlTagProviding.swift)
target_compile_definitions(Yams PRIVATE
SWIFT_PACKAGE)
target_compile_options(Yams PRIVATE
Expand Down
18 changes: 10 additions & 8 deletions Sources/Yams/Constructor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ public final class Constructor {
if let method = mappingMap[node.tag.name], let result = method(mapping) {
return result
}
return [AnyHashable: Any]._construct_mapping(from: mapping)
return [AnyHashable: Any].private_construct_mapping(from: mapping)
case .sequence(let sequence):
if let method = sequenceMap[node.tag.name], let result = method(sequence) {
return result
}
return [Any].construct_seq(from: sequence)
case .alias:
preconditionFailure("Aliases should be resolved before construction")
}
}

Expand Down Expand Up @@ -270,7 +272,7 @@ extension ScalarConstructible where Self: FloatingPoint & SexagesimalConvertible
}

private extension FixedWidthInteger where Self: SexagesimalConvertible {
static func _construct(from scalar: Node.Scalar) -> Self? {
static func private_construct(from scalar: Node.Scalar) -> Self? {
guard scalar.style == .any || scalar.style == .plain else {
return nil
}
Expand Down Expand Up @@ -315,7 +317,7 @@ extension Int: ScalarConstructible {
///
/// - returns: An instance of `Int`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Int? {
return _construct(from: scalar)
return private_construct(from: scalar)
}
}

Expand All @@ -328,7 +330,7 @@ extension UInt: ScalarConstructible {
///
/// - returns: An instance of `UInt`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> UInt? {
return _construct(from: scalar)
return private_construct(from: scalar)
}
}

Expand All @@ -341,7 +343,7 @@ extension Int64: ScalarConstructible {
///
/// - returns: An instance of `Int64`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Int64? {
return _construct(from: scalar)
return private_construct(from: scalar)
}
}

Expand All @@ -354,7 +356,7 @@ extension UInt64: ScalarConstructible {
///
/// - returns: An instance of `UInt64`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> UInt64? {
return _construct(from: scalar)
return private_construct(from: scalar)
}
}

Expand Down Expand Up @@ -418,12 +420,12 @@ extension Dictionary {
///
/// - returns: An instance of `[AnyHashable: Any]`, if one was successfully extracted from the mapping.
public static func construct_mapping(from mapping: Node.Mapping) -> [AnyHashable: Any]? {
return _construct_mapping(from: mapping)
return private_construct_mapping(from: mapping)
}
}

private extension Dictionary {
static func _construct_mapping(from mapping: Node.Mapping) -> [AnyHashable: Any] {
static func private_construct_mapping(from mapping: Node.Mapping) -> [AnyHashable: Any] {
let mapping = mapping.flatten()
// TODO: YAML supports keys other than str.
return [AnyHashable: Any](
Expand Down
53 changes: 50 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 Expand Up @@ -381,3 +426,5 @@ extension YAMLDecoder: TopLevelDecoder {
}
}
#endif

// swiftlint:disable:this file_length
Loading

0 comments on commit 2688707

Please sign in to comment.