1
1
mirror of https://github.com/github/semantic.git synced 2024-12-22 22:31:36 +03:00
semantic/prototype/Doubt/Patch.swift

176 lines
3.9 KiB
Swift

/// A patch to some part of a `Syntax` tree.
public enum Patch<A>: CustomDebugStringConvertible {
case Replace(A, A)
case Insert(A)
case Delete(A)
public var state: (before: A?, after: A?) {
switch self {
case let .Replace(a, b):
return (a, b)
case let .Insert(b):
return (nil, b)
case let .Delete(a):
return (a, nil)
}
}
public var inverse: Patch {
switch self {
case let .Replace(a, b):
return .Replace(b, a)
case let .Insert(b):
return .Delete(b)
case let .Delete(a):
return .Insert(a)
}
}
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
switch self {
case let .Replace(a, b):
return ".Replace(\(String(reflecting: a)), \(String(reflecting: b)))"
case let .Insert(b):
return ".Insert(\(String(reflecting: b)))"
case let .Delete(a):
return ".Delete(\(String(reflecting: a)))"
}
}
}
// MARK: - Functor
extension Patch {
public func map<B>(@noescape transform: A throws -> B) rethrows -> Patch<B> {
switch self {
case let .Replace(a, b):
return try .Replace(transform(a), transform(b))
case let .Delete(a):
return try .Delete(transform(a))
case let .Insert(b):
return try .Insert(transform(b))
}
}
}
// MARK: - Equality
extension Patch {
public static func equals(param: (A, A) -> Bool)(_ left: Patch, _ right: Patch) -> Bool {
return Optional.equals(param)(left.state.before, right.state.before)
&& Optional.equals(param)(left.state.after, right.state.after)
}
}
// MARK: - Cost calculations
extension Patch {
/// Returns a function which computes the size of a `patch` as the sum of the sizes of its terms, as computed by `size`.
public static func sum(@noescape size: A -> Int)(_ patch: Patch) -> Int {
switch patch {
case let .Replace(a, b):
return size(a) + size(b)
case let .Insert(b):
return size(b)
case let .Delete(a):
return size(a)
}
}
/// Returns a function which computes the size of a `patch` as the absolute difference of the sizes of its terms, as computed by `size`.
public static func difference(@noescape size: A -> Int)(_ patch: Patch) -> Int {
return abs((patch.state.before.map(size) ?? 0) - (patch.state.after.map(size) ?? 0))
}
}
extension Patch where A: TermType {
/// Computes the size of a `patch` as the sum of the sizes of its terms.
public static func sum(patch: Patch) -> Int {
return sum { $0.size } (patch)
}
/// Computes the size of a `patch` as the absolute difference of the sizes of its terms.
public static func difference(patch: Patch) -> Int {
return difference { $0.size } (patch)
}
}
// MARK: - JSON
extension Patch {
public func JSON(ifLeaf: A -> Doubt.JSON) -> Doubt.JSON {
switch self {
case let .Replace(a, b):
return [
"replace": [
"before": ifLeaf(a),
"after": ifLeaf(b),
]
]
case let .Insert(b):
return [
"insert": ifLeaf(b),
]
case let .Delete(a):
return [
"delete": ifLeaf(a)
]
}
}
}
extension Patch where A: CustomJSONConvertible {
public var JSON: Doubt.JSON {
return JSON { $0.JSON }
}
}
// MARK: - PatchType
/// A hack to enable constrained extensions on `Free<Leaf, Annotation, Patch<Term: CofreeType where Term.Leaf == Leaf, Term.Annotation == Annotation>`.
public protocol PatchType {
typealias Element
var state: (before: Element?, after: Element?) { get }
var inverse: Self { get }
init(replacing before: Element, with after: Element)
init(deleting before: Element)
init(inserting after: Element)
}
extension Patch: PatchType {
public init(replacing before: A, with after: A) {
self = .Replace(before, after)
}
public init(deleting before: A) {
self = .Delete(before)
}
public init(inserting after: A) {
self = .Insert(after)
}
}
extension PatchType where Element: CofreeType, Element.Annotation == Range<String.Index> {
public func JSON(a a: String, b: String) -> Doubt.JSON {
return [
"before": state.before.map { $0.JSON(a) } ?? nil,
"after": state.after.map { $0.JSON(b) } ?? nil,
]
}
}