Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use more effectively that Node is an enum #105

Merged
merged 5 commits into from
Feb 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@

##### Breaking

* None.
* Some APIs have changed related to `ScalarConstructible`
* Change parameter type of `ScalarConstructible.construct(from:)` from `Node`
to `Node.Scalar`
* Change `Constructor`:
* Split `Map` into `ScalarMap`, `MappingMap` and `SequenceMap`
* Split `defaultMap` into `defaultScalarMap`, `defaultMappingMap` and
`defaultSequenceMap`
* Change `init(_:)` to `init(_:_:_:)`

[Norio Nomura](https://github.com/norio-nomura)
[#105](https://github.com/jpsim/Yams/issues/105)

##### Enhancements

Expand Down
190 changes: 106 additions & 84 deletions Sources/Yams/Constructor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,65 +9,86 @@
import Foundation

public final class Constructor {
public typealias Map = [Tag.Name: (Node) -> Any?]

public init(_ map: Map) {
methodMap = map
public typealias ScalarMap = [Tag.Name: (Node.Scalar) -> Any?]
public typealias MappingMap = [Tag.Name: (Node.Mapping) -> Any?]
public typealias SequenceMap = [Tag.Name: (Node.Sequence) -> Any?]

public init(_ scalarMap: ScalarMap = defaultScalarMap,
_ mappingMap: MappingMap = defaultMappingMap,
_ sequenceMap: SequenceMap = defaultSequenceMap) {
self.scalarMap = scalarMap
self.mappingMap = mappingMap
self.sequenceMap = sequenceMap
}

public func any(from node: Node) -> Any {
if let method = methodMap[node.tag.name], let result = method(node) {
return result
}
switch node {
case .scalar:
return String.construct(from: node)!
case .mapping:
return [AnyHashable: Any]._construct_mapping(from: node)
case .sequence:
return [Any].construct_seq(from: node)
case .scalar(let scalar):
if let method = scalarMap[node.tag.name], let result = method(scalar) {
return result
}
return String.construct(from: scalar)!
case .mapping(let mapping):
if let method = mappingMap[node.tag.name], let result = method(mapping) {
return result
}
return [AnyHashable: Any]._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)
}
}

private let methodMap: Map
private let scalarMap: ScalarMap
private let mappingMap: MappingMap
private let sequenceMap: SequenceMap
}

extension Constructor {
public static let `default` = Constructor(defaultMap)
public static let `default` = Constructor()

// We can not write extension of map because that is alias of specialized dictionary
public static let defaultMap: Map = [
public static let defaultScalarMap: ScalarMap = [
// Failsafe Schema
.map: [AnyHashable: Any].construct_mapping,
.str: String.construct,
.seq: [Any].construct_seq,
// JSON Schema
.bool: Bool.construct,
.float: Double.construct,
.null: NSNull.construct,
.int: Int.construct,
// http://yaml.org/type/index.html
.binary: Data.construct,
// .merge is supported in `[AnyHashable: Any].construct`.
.omap: [Any].construct_omap,
.pairs: [Any].construct_pairs,
.set: Set<AnyHashable>.construct_set,
.timestamp: Date.construct
// .value is supported in `String.construct` and `[AnyHashable: Any].construct`.
]

public static let defaultMappingMap: MappingMap = [
.map: [AnyHashable: Any].construct_mapping,
// http://yaml.org/type/index.html
.set: Set<AnyHashable>.construct_set
// .merge is supported in `[AnyHashable: Any].construct_mapping`.
// .value is supported in `String.construct` and `[AnyHashable: Any].construct_mapping`.
]

public static let defaultSequenceMap: SequenceMap = [
.seq: [Any].construct_seq,
// http://yaml.org/type/index.html
.omap: [Any].construct_omap,
.pairs: [Any].construct_pairs
]
}

// MARK: - ScalarConstructible
public protocol ScalarConstructible {
// We don't use overloading `init?(_ node: Node)`
// We don't use overloading `init?(_ scalar: Node.Scalar)`
// because that causes difficulties on using `init` as closure
static func construct(from node: Node) -> Self?
static func construct(from scalar: Node.Scalar) -> Self?
}

extension Bool: ScalarConstructible {
public static func construct(from node: Node) -> Bool? {
guard let string = node.scalar?.string else { return nil }
switch string.lowercased() {
public static func construct(from scalar: Node.Scalar) -> Bool? {
switch scalar.string.lowercased() {
case "true", "yes", "on":
return true
case "false", "no", "off":
Expand All @@ -79,28 +100,25 @@ extension Bool: ScalarConstructible {
}

extension Data: ScalarConstructible {
public static func construct(from node: Node) -> Data? {
guard let string = node.scalar?.string else { return nil }
return Data(base64Encoded: string, options: .ignoreUnknownCharacters)
public static func construct(from scalar: Node.Scalar) -> Data? {
return Data(base64Encoded: scalar.string, options: .ignoreUnknownCharacters)
}
}

extension Date: ScalarConstructible {
public static func construct(from node: Node) -> Date? {
guard let string = node.scalar?.string else { return nil }

let range = NSRange(location: 0, length: string.utf16.count)
guard let result = timestampPattern.firstMatch(in: string, options: [], range: range),
public static func construct(from scalar: Node.Scalar) -> Date? {
let range = NSRange(location: 0, length: scalar.string.utf16.count)
guard let result = timestampPattern.firstMatch(in: scalar.string, options: [], range: range),
result.range.location != NSNotFound else {
return nil
}
#if os(Linux) || swift(>=4.0)
let components = (1..<result.numberOfRanges).map {
string.substring(with: result.range(at: $0))
scalar.string.substring(with: result.range(at: $0))
}
#else
let components = (1..<result.numberOfRanges).map {
string.substring(with: result.rangeAt($0))
scalar.string.substring(with: result.rangeAt($0))
}
#endif

Expand Down Expand Up @@ -158,17 +176,16 @@ extension Double: ScalarConstructible {}
extension Float: ScalarConstructible {}

extension ScalarConstructible where Self: FloatingPoint & SexagesimalConvertible {
public static func construct(from node: Node) -> Self? {
guard var string = node.scalar?.string else { return nil }
switch string {
public static func construct(from scalar: Node.Scalar) -> Self? {
switch scalar.string {
case ".inf", ".Inf", ".INF", "+.inf", "+.Inf", "+.INF":
return .infinity
case "-.inf", "-.Inf", "-.INF":
return -Self.infinity
case ".nan", ".NaN", ".NAN":
return .nan
default:
string = string.replacingOccurrences(of: "_", with: "")
let string = scalar.string.replacingOccurrences(of: "_", with: "")
if string.contains(":") {
return Self(sexagesimal: string)
}
Expand All @@ -178,10 +195,8 @@ extension ScalarConstructible where Self: FloatingPoint & SexagesimalConvertible
}

extension FixedWidthInteger where Self: SexagesimalConvertible {
fileprivate static func _construct(from node: Node) -> Self? {
guard let string = node.scalar?.string else { return nil }

let scalarWithSign = string.replacingOccurrences(of: "_", with: "")
fileprivate static func _construct(from scalar: Node.Scalar) -> Self? {
let scalarWithSign = scalar.string.replacingOccurrences(of: "_", with: "")

if scalarWithSign == "0" {
return 0
Expand Down Expand Up @@ -213,18 +228,22 @@ extension FixedWidthInteger where Self: SexagesimalConvertible {
}

extension Int: ScalarConstructible {
public static func construct(from node: Node) -> Int? {
return _construct(from: node)
public static func construct(from scalar: Node.Scalar) -> Int? {
return _construct(from: scalar)
}
}

extension UInt: ScalarConstructible {
public static func construct(from node: Node) -> UInt? {
return _construct(from: node)
public static func construct(from scalar: Node.Scalar) -> UInt? {
return _construct(from: scalar)
}
}

extension String: ScalarConstructible {
public static func construct(from scalar: Node.Scalar) -> String? {
return scalar.string
}

public static func construct(from node: Node) -> String? {
// This will happen while `Dictionary.flatten_mapping()` if `node.tag.name` was `.value`
if case let .mapping(mapping) = node {
Expand All @@ -240,9 +259,8 @@ extension String: ScalarConstructible {

// MARK: - Types that can't conform to ScalarConstructible
extension NSNull/*: ScalarConstructible*/ {
public static func construct(from node: Node) -> NSNull? {
guard let string = node.scalar?.string else { return nil }
switch string {
public static func construct(from scalar: Node.Scalar) -> NSNull? {
switch scalar.string {
case "", "~", "null", "Null", "NULL":
return NSNull()
default:
Expand All @@ -253,24 +271,21 @@ extension NSNull/*: ScalarConstructible*/ {

// MARK: mapping
extension Dictionary {
public static func construct_mapping(from node: Node) -> [AnyHashable: Any]? {
return _construct_mapping(from: node)
public static func construct_mapping(from mapping: Node.Mapping) -> [AnyHashable: Any]? {
return _construct_mapping(from: mapping)
}

fileprivate static func _construct_mapping(from node: Node) -> [AnyHashable: Any] {
assert(node.isMapping) // swiftlint:disable:next force_unwrapping
let mapping = flatten_mapping(node).mapping!
fileprivate static func _construct_mapping(from mapping: Node.Mapping) -> [AnyHashable: Any] {
let mapping = flatten_mapping(mapping)
var dictionary = [AnyHashable: Any](minimumCapacity: mapping.count)
mapping.forEach {
// TODO: YAML supports keys other than str.
dictionary[String.construct(from: $0.key)!] = node.tag.constructor.any(from: $0.value)
dictionary[String.construct(from: $0.key)!] = mapping.tag.constructor.any(from: $0.value)
}
return dictionary
}

private static func flatten_mapping(_ node: Node) -> Node {
assert(node.isMapping) // swiftlint:disable:next force_unwrapping
let mapping = node.mapping!
private static func flatten_mapping(_ mapping: Node.Mapping) -> Node.Mapping {
var pairs = Array(mapping)
var merge = [(key: Node, value: Node)]()
var index = pairs.startIndex
Expand All @@ -279,15 +294,11 @@ extension Dictionary {
if pair.key.tag.name == .merge {
pairs.remove(at: index)
switch pair.value {
case .mapping:
let flattened_node = flatten_mapping(pair.value)
if let mapping = flattened_node.mapping {
merge.append(contentsOf: mapping)
}
case .mapping(let mapping):
merge.append(contentsOf: flatten_mapping(mapping))
case let .sequence(sequence):
let submerge = sequence
.filter { $0.isMapping } // TODO: Should raise error on other than mapping
.compactMap { flatten_mapping($0).mapping }
.compactMap { $0.mapping.map(flatten_mapping) }
.reversed()
submerge.forEach {
merge.append(contentsOf: $0)
Expand All @@ -302,44 +313,40 @@ extension Dictionary {
index += 1
}
}
return Node(merge + pairs, node.tag, mapping.style)
return Node.Mapping(merge + pairs, mapping.tag, mapping.style)
}
}

extension Set {
public static func construct_set(from node: Node) -> Set<AnyHashable>? {
public static func construct_set(from mapping: Node.Mapping) -> Set<AnyHashable>? {
// TODO: YAML supports Hashable elements other than str.
assert(node.isMapping) // swiftlint:disable:next force_unwrapping
return Set<AnyHashable>(node.mapping!.map({ String.construct(from: $0.key)! as AnyHashable }))
return Set<AnyHashable>(mapping.map({ String.construct(from: $0.key)! as AnyHashable }))
// Explicitly declaring the generic parameter as `<AnyHashable>` above is required,
// because this is inside extension of `Set` and Swift 3.0.2 can't infer the type without that.
}
}

// MARK: sequence
extension Array {
public static func construct_seq(from node: Node) -> [Any] {
// swiftlint:disable:next force_unwrapping
return node.sequence!.map(node.tag.constructor.any)
public static func construct_seq(from sequence: Node.Sequence) -> [Any] {
return sequence.map(sequence.tag.constructor.any)
}

public static func construct_omap(from node: Node) -> [(Any, Any)] {
public static func construct_omap(from sequence: Node.Sequence) -> [(Any, Any)] {
// Note: we do not check for duplicate keys.
assert(node.isSequence) // swiftlint:disable:next force_unwrapping
return node.sequence!.compactMap { subnode -> (Any, Any)? in
return sequence.compactMap { subnode -> (Any, Any)? in
// TODO: Should raise error if subnode is not mapping or mapping.count != 1
guard let (key, value) = subnode.mapping?.first else { return nil }
return (node.tag.constructor.any(from: key), node.tag.constructor.any(from: value))
return (sequence.tag.constructor.any(from: key), sequence.tag.constructor.any(from: value))
}
}

public static func construct_pairs(from node: Node) -> [(Any, Any)] {
public static func construct_pairs(from sequence: Node.Sequence) -> [(Any, Any)] {
// Note: we do not check for duplicate keys.
assert(node.isSequence) // swiftlint:disable:next force_unwrapping
return node.sequence!.compactMap { subnode -> (Any, Any)? in
return sequence.compactMap { subnode -> (Any, Any)? in
// TODO: Should raise error if subnode is not mapping or mapping.count != 1
guard let (key, value) = subnode.mapping?.first else { return nil }
return (node.tag.constructor.any(from: key), node.tag.constructor.any(from: value))
return (sequence.tag.constructor.any(from: key), sequence.tag.constructor.any(from: value))
}
}
}
Expand Down Expand Up @@ -437,4 +444,19 @@ fileprivate extension Substring {
}
}

// MARK: - Unavailable

extension Constructor {
@available(*, unavailable, message: "Use `Constructor.ScalarMap` instead")
public typealias Map = [Tag.Name: (Node) -> Any?]

@available(*, unavailable, message: "Use `Constructor.defaultScalarMap` instead")
public static let defaultMap: ScalarMap = [:]
}

extension ScalarConstructible {
@available(*, unavailable, message: "Use `construct(from scalar: Node.Scalar)` instead")
static func construct(from scalar: Node) -> Self? { return nil }
}

// swiftlint:disable:this file_length
Loading