1
1
mirror of https://github.com/github/semantic.git synced 2024-11-29 11:02:26 +03:00

Merge pull request #62 from github/update-swift-target

Update Swift target
This commit is contained in:
Rob Rix 2015-10-09 08:03:56 -05:00
commit 26f8e63ee3
4 changed files with 320 additions and 27 deletions

View File

@ -61,6 +61,16 @@
ReferencedContainer = "container:Doubt.xcodeproj"> ReferencedContainer = "container:Doubt.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "$(SRCROOT)/doubt-swift/Fixtures/Algorithm.old.swift"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "$(SRCROOT)/doubt-swift/Fixtures/Algorithm.new.swift"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>

View File

@ -0,0 +1,146 @@
/// An operation of diffing over terms or collections of terms.
public enum Algorithm<Recur, A> {
/// The type of `Term`s over which `Algorithm`s operate.
public typealias Term = Fix<A>
/// The type of `Diff`s which `Algorithm`s produce.
public typealias Diff = Free<A, Patch<A>>
/// Indicates that diffing should compare the enclosed `Term`s.
///
/// When run, the enclosed function will be applied to the resulting `Diff`.
case Recursive(Term, Term, Diff -> Recur)
/// Represents a diff to be performed on a collection of terms identified by keys.
case ByKey([String:Term], [String:Term], [String:Diff] -> Recur)
/// Represents a diff to be performed over an array of terms by index.
case ByIndex([Term], [Term], [Diff] -> Recur)
// MARK: Functor
public func map<Other>(transform: Recur -> Other) -> Algorithm<Other, A> {
switch self {
case let .Recursive(a, b, f):
return .Recursive(a, b, f >>> transform)
case let .ByKey(a, b, f):
return .ByKey(a, b, f >>> transform)
case let .ByIndex(a, b, f):
return .ByIndex(a, b, f >>> transform)
}
}
}
/// The free monad over `Algorithm`, implementing the language of diffing.
///
/// As with `Free`, this is free in the sense of unconstrained, i.e. the monad induced by `Algorithm` without extra assumptions.
///
/// Where `Algorithm` models a single diffing strategy, `FreeAlgorithm` models the recursive selection of diffing strategies at each node. Thus, a value in `FreeAlgorithm` models an algorithm for constructing a value in the type `B` from the resulting diffs. By this means, diffing can be adapted not just to the specific grammar, but to specific trees produced by that grammar, and even the values of type `A` encapsulated at each node.
public enum FreeAlgorithm<A, B> {
/// The type of `Term`s over which `FreeAlgorithm`s operate.
public typealias Term = Algorithm<FreeAlgorithm, A>.Term
/// The type of `Diff`s which `FreeAlgorithm`s produce.
public typealias Diff = Algorithm<FreeAlgorithm, A>.Diff
/// The injection of a value of type `B` into an `Algorithm`.
///
/// Equally, a way to return a result or throw an error during computation, as determined by the type which `B` is instantiated to, and the specific context in which it is being evaluated.
case Pure(B)
/// A recursive instantiation of `Algorithm`, unrolling another iteration of the recursive type.
case Roll(Algorithm<FreeAlgorithm, A>)
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Algorithm<FreeAlgorithm, A> -> C) -> C {
switch self {
case let .Pure(b):
return ifPure(b)
case let .Roll(a):
return ifRoll(a)
}
}
// MARK: Functor
public func map<Other>(transform: B -> Other) -> FreeAlgorithm<A, Other> {
return analysis(ifPure: transform >>> FreeAlgorithm<A, Other>.Pure, ifRoll: { .Roll($0.map { $0.map(transform) }) })
}
// MARK: Monad
public func flatMap<C>(transform: B -> FreeAlgorithm<A, C>) -> FreeAlgorithm<A, C> {
return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) })
}
/// Evaluates the encoded algorithm, returning its result.
public func evaluate(equals: (A, A) -> Bool, recur: (Term, Term) -> Diff) -> B {
switch self {
case let .Pure(b):
return b
case let .Roll(.Recursive(a, b, f)):
return f(Term.equals(equals)(a, b)
? Diff(b)
// This must not call `recur` with `a` and `b`, as that would infinite loop if actually recursive.
: Diff.Pure(.Replace(a, b))).evaluate(equals, recur: recur)
case let .Roll(.ByKey(a, b, f)):
let deleted = Set(a.keys).subtract(b.keys).map { ($0, Diff.Pure(Patch.Delete(a[$0]!))) }
let inserted = Set(b.keys).subtract(a.keys).map { ($0, Diff.Pure(Patch.Insert(b[$0]!))) }
let patched = Set(a.keys).intersect(b.keys).map { ($0, recur(a[$0]!, b[$0]!)) }
return f(Dictionary(elements: deleted + inserted + patched)).evaluate(equals, recur: recur)
case let .Roll(.ByIndex(a, b, f)):
return f(SES(a, b, equals: equals, recur: recur)).evaluate(equals, recur: recur)
}
}
}
extension FreeAlgorithm where A: Equatable {
public func evaluate(recur: (Term, Term) -> Diff) -> B {
return evaluate(==, recur: recur)
}
}
/// A hack to work around the unavailability of same-type requirements.
public protocol FreeConvertible {
typealias RollType
typealias PureType
init(free: Free<RollType, PureType>)
var free: Free<RollType, PureType> { get }
}
extension Free: FreeConvertible {
public init(free: Free<A, B>) { self = free }
public var free: Free { return self }
}
extension FreeAlgorithm where B: FreeConvertible, B.RollType == A, B.PureType == Patch<A> {
/// `FreeAlgorithm<A, Diff>`s can be constructed from a pair of `Term`s using `ByKey` when `Keyed`, `ByIndex` when `Indexed`, and `Recursive` otherwise.
public init(_ a: Term, _ b: Term) {
switch (a.out, b.out) {
case let (.Keyed(a), .Keyed(b)):
self = .Roll(.ByKey(a, b, Syntax.Keyed >>> Free.Roll >>> B.init >>> Pure))
case let (.Indexed(a), .Indexed(b)):
self = .Roll(.ByIndex(a, b, Syntax.Indexed >>> Free.Roll >>> B.init >>> Pure))
default:
self = .Roll(.Recursive(a, b, B.init >>> FreeAlgorithm.Pure))
}
}
public func evaluate(equals: (A, A) -> Bool) -> B {
return evaluate(equals, recur: { FreeAlgorithm($0, $1).evaluate(equals).free })
}
}
extension FreeAlgorithm where A: Equatable, B: FreeConvertible, B.RollType == A, B.PureType == Patch<A> {
public func evaluate() -> B {
return evaluate(==)
}
}

View File

@ -0,0 +1,136 @@
/// A language of diffing algorithms.
public enum Algorithm<Recur, A> {
/// The type of `Term`s over which `Algorithm`s operate.
public typealias Term = Fix<A>
/// The type of `Diff`s which `Algorithm`s produce.
public typealias Diff = Free<A, Patch<A>>
/// Indicates that diffing should compare the enclosed `Term`s.
///
/// When run, the enclosed function will be applied to the resulting `Diff`.
case Recursive(Term, Term, Diff -> Recur)
/// Represents a diff to be performed on a collection of terms identified by keys.
case ByKey([String:Term], [String:Term], [String:Diff] -> Recur)
// fixme: SES 😰
// case ByIndex([Term], [Term], [Diff] -> Recur)
// MARK: Functor
public func map<Other>(transform: Recur -> Other) -> Algorithm<Other, A> {
switch self {
case let .Recursive(a, b, f):
return .Recursive(a, b, f >>> transform)
case let .ByKey(a, b, f):
return .ByKey(a, b, f >>> transform)
}
}
}
// MARK: - Running
extension Algorithm {
/// Evaluates the encoded algorithm, returning its result.
public func evaluate(equals: (A, A) -> Bool) -> Recur {
/// Deep-copies a `Term` into a `Diff` without changes.
func copy(b: Term) -> Diff {
return Diff.Roll(b.out.map(copy))
}
switch self {
case let .Recursive(a, b, f):
return f(Fix.equals(equals)(a, b)
? copy(b)
: Diff.Pure(.Replace(a, b)))
case let .ByKey(a, b, f):
let deleted = Set(a.keys).subtract(b.keys).map { ($0, Diff.Pure(Patch.Delete(a[$0]!))) }
let inserted = Set(b.keys).subtract(a.keys).map { ($0, Diff.Pure(Patch.Insert(b[$0]!))) }
let patched = Set(a.keys).intersect(b.keys).map { ($0, Diff.Pure(Patch.Replace(a[$0]!, b[$0]!))) }
return f(Dictionary(elements: deleted + inserted + patched))
}
}
}
/// The free monad over `Algorithm`.
///
/// As with `Free`, this is free in the sense of unconstrained, i.e. the monad induced by `Algorithm` without extra assumptions.
public enum FreeAlgorithm<A, B> {
/// The injection of a value of type `B` into an `Algorithm`.
///
/// Equally, a way to return a result or throw an error during computation, as determined by the type which `B` is instantiated to, and the specific context in which it is being evaluated.
case Pure(B)
/// A recursive instantiation of `Algorithm`, unrolling another iteration of the recursive type.
case Roll(Algorithm<FreeAlgorithm, A>)
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Algorithm<FreeAlgorithm, A> -> C) -> C {
switch self {
case let .Pure(b):
return ifPure(b)
case let .Roll(a):
return ifRoll(a)
}
}
// MARK: Functor
public func map<Other>(transform: B -> Other) -> FreeAlgorithm<A, Other> {
return analysis(ifPure: transform >>> FreeAlgorithm<A, Other>.Pure, ifRoll: { .Roll($0.map { $0.map(transform) }) })
}
// MARK: Monad
public func flatMap<C>(transform: B -> FreeAlgorithm<A, C>) -> FreeAlgorithm<A, C> {
return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) })
}
/// Evaluates the encoded algorithm, returning its result.
public func evaluate(equals: (A, A) -> Bool) -> B {
switch self {
case let .Pure(b):
return b
case let .Roll(r):
return r.evaluate(equals).evaluate(equals)
}
}
}
extension FreeAlgorithm where A: Equatable {
public func evaluate() -> B {
return evaluate(==)
}
}
/// A hack to work around the unavailability of same-type requirements.
public protocol FreeConvertible {
typealias RollType
typealias PureType
init(free: Free<RollType, PureType>)
var free: Free<RollType, PureType> { get }
}
extension Free: FreeConvertible {
public init(free: Free<A, B>) { self = free }
public var free: Free { return self }
}
extension FreeAlgorithm where B: FreeConvertible, B.RollType == A, B.PureType == Patch<A> {
public init(_ a: Fix<A>, _ b: Fix<A>) {
switch (a.out, b.out) {
case let (.Keyed(a), .Keyed(b)):
self = .Roll(.ByKey(a, b, Syntax.Keyed >>> Free.Roll >>> B.init >>> FreeAlgorithm.Pure))
default:
self = .Roll(.Recursive(a, b, B.init >>> FreeAlgorithm.Pure))
}
}
}

View File

@ -13,10 +13,16 @@ extension String: StringConvertible {
} }
} }
extension Info: StringConvertible {
public init(string: String) {
self = .Literal(string, [])
}
}
private struct Bail: ErrorType {} private struct Bail: ErrorType {}
extension Term where A: StringConvertible { extension Fix where A: StringConvertible {
/// Constructs a Term representing `JSON`. /// Constructs a term representing `JSON`.
init?(JSON: Doubt.JSON) { init?(JSON: Doubt.JSON) {
func bail<B>() throws -> B { func bail<B>() throws -> B {
throw Bail() throw Bail()
@ -29,23 +35,24 @@ extension Term where A: StringConvertible {
let kind = d["key.kind"]?.string let kind = d["key.kind"]?.string
switch kind { switch kind {
case case
.Some("source.lang.swift.decl.class"), .Some("source.lang.swift.decl.class"),
.Some("source.lang.swift.decl.extension"), .Some("source.lang.swift.decl.extension"),
.Some("source.lang.swift.decl.enum"), .Some("source.lang.swift.decl.enum"),
.Some("source.lang.swift.decl.struct"): .Some("source.lang.swift.decl.struct"),
self = .Branch([ .Leaf(A(string: name)), .Branch(try substructure.map { try Term(JSON: $0) ?? bail() }) ]) .Some("source.lang.swift.decl.protocol"):
self = .In(.Indexed([ .In(.Leaf(A(string: name))), .In(.Indexed(try substructure.map { try Fix(JSON: $0) ?? bail() })) ]))
case .Some("source.lang.swift.decl.enumelement"): case .Some("source.lang.swift.decl.enumelement"):
fallthrough fallthrough
case case
.Some("source.lang.swift.decl.function.method.instance"), .Some("source.lang.swift.decl.function.method.instance"),
.Some("source.lang.swift.decl.function.free"): .Some("source.lang.swift.decl.function.free"):
self = .Branch([ .Leaf(A(string: name)), .Branch(try substructure.map { try Term(JSON: $0) ?? bail() }) ]) self = .In(.Indexed([ .In(.Leaf(A(string: name))), .In(.Indexed(try substructure.map { try Fix(JSON: $0) ?? bail() })) ]))
case case
.Some("source.lang.swift.decl.var.instance"), .Some("source.lang.swift.decl.var.instance"),
.Some("source.lang.swift.decl.var.static"): .Some("source.lang.swift.decl.var.static"):
self = .Leaf(A(string: name)) self = .In(.Leaf(A(string: name)))
default: default:
return nil return nil
@ -53,13 +60,10 @@ extension Term where A: StringConvertible {
case let .Dictionary(d) where d["key.kind"]?.string == "source.lang.swift.decl.enumcase" && d["key.substructure"]?.array?.count == 1: case let .Dictionary(d) where d["key.kind"]?.string == "source.lang.swift.decl.enumcase" && d["key.substructure"]?.array?.count == 1:
let substructure = d["key.substructure"]?.array ?? [] let substructure = d["key.substructure"]?.array ?? []
self = try Term(JSON: substructure[0]) ?? bail() self = try Fix(JSON: substructure[0]) ?? bail()
case let .Dictionary(d) where d["key.kind"]?.string == "source.lang.swift.syntaxtype.comment.mark": case let .Dictionary(d) where d["key.kind"]?.string == "source.lang.swift.syntaxtype.comment.mark":
self = .Empty self = .In(.Leaf(A(string: "mark")))
case .Null:
self = .Empty
default: default:
return nil return nil
@ -69,7 +73,7 @@ extension Term where A: StringConvertible {
} }
} }
/// Constructs a Term representing the `JSON` in a file at `path`. /// Constructs a term representing the `JSON` in a file at `path`.
init?(path: String, JSON: Doubt.JSON) { init?(path: String, JSON: Doubt.JSON) {
func bail<B>() throws -> B { func bail<B>() throws -> B {
throw Bail() throw Bail()
@ -77,7 +81,7 @@ extension Term where A: StringConvertible {
do { do {
switch JSON.dictionary?["key.substructure"] { switch JSON.dictionary?["key.substructure"] {
case let .Some(.Array(a)): case let .Some(.Array(a)):
self = .Roll(.Branch(try a.map { try Term(JSON: $0) ?? bail() })) self = .In(.Indexed(try a.map { try Fix(JSON: $0) ?? bail() }))
default: default:
return nil return nil
} }
@ -85,23 +89,20 @@ extension Term where A: StringConvertible {
return nil return nil
} }
} }
}
extension Term where A: StringConvertible {
init?(path: String) { init?(path: String) {
guard path != "/dev/null" else { guard path != "/dev/null" else {
self = .Empty return nil
return
} }
guard let term = File(path: path) guard let term = File(path: path)
.map(Structure.init) .map(Structure.init)
.map({ $0.dictionary }) .map({ $0.dictionary })
.map(toAnyObject) .map(toAnyObject)
.flatMap({ JSON(object: $0).flatMap { Term(path: path, JSON: $0) } }) else { return nil } .flatMap({ JSON(object: $0).flatMap { Fix(path: path, JSON: $0) } }) else { return nil }
self = term self = term
} }
} }
if let a = arguments[1].flatMap(Term<Info>.init), b = arguments[2].flatMap(Term<Info>.init) { if let a = arguments[1].flatMap({ Fix<Info>(path: $0) }), b = arguments[2].flatMap({ Fix<Info>(path: $0) }) {
print(String(reflecting: Diff(a, b))) print(String(reflecting: FreeAlgorithm<Info, Free<Info, Patch<Info>>>(a, b).evaluate()))
} }