diff --git a/prototype/Doubt.xcodeproj/project.pbxproj b/prototype/Doubt.xcodeproj/project.pbxproj index 314c4060a..09e3336b6 100644 --- a/prototype/Doubt.xcodeproj/project.pbxproj +++ b/prototype/Doubt.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ D4C2B1421BB3474C0096F92A /* SwiftXPC.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4413FD61BB0531E00E3C3C1 /* SwiftXPC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D4C2B1431BB3474C0096F92A /* SourceKittenFramework.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4413FD41BB052ED00E3C3C1 /* SourceKittenFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D4C2B1441BB347500096F92A /* Doubt.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4AAE4FD1B5AE22E004E581F /* Doubt.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D4D7F3171BBB22E500AAB0C0 /* Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D7F3161BBB22E500AAB0C0 /* Hash.swift */; settings = {ASSET_TAGS = (); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -109,6 +110,7 @@ D4AAE53E1B5AE2D0004E581F /* StringLiteralConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringLiteralConvertible.swift; sourceTree = ""; }; D4AAE53F1B5AE2D0004E581F /* Syntax.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Syntax.swift; sourceTree = ""; }; D4AAE54B1B5AE42D004E581F /* Equatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Equatable.swift; sourceTree = ""; }; + D4D7F3161BBB22E500AAB0C0 /* Hash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Hash.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -196,6 +198,7 @@ D4413FEE1BB06D4C00E3C3C1 /* Dictionary.swift */, D4413FF01BB08FDC00E3C3C1 /* JSON.swift */, D4A71DC41BB45B850051416D /* Vertex.swift */, + D4D7F3161BBB22E500AAB0C0 /* Hash.swift */, D4AAE5001B5AE22E004E581F /* Supporting Files */, ); path = Doubt; @@ -375,6 +378,7 @@ D4413FEF1BB06D4C00E3C3C1 /* Dictionary.swift in Sources */, D4AAE5481B5AE2D0004E581F /* String.swift in Sources */, D4AAE5411B5AE2D0004E581F /* Diff.swift in Sources */, + D4D7F3171BBB22E500AAB0C0 /* Hash.swift in Sources */, D4AAE5401B5AE2D0004E581F /* Array.swift in Sources */, D4AAE54C1B5AE42D004E581F /* Equatable.swift in Sources */, D4A71DC51BB45B850051416D /* Vertex.swift in Sources */, diff --git a/prototype/Doubt/Hash.swift b/prototype/Doubt/Hash.swift new file mode 100644 index 000000000..462b2f9f7 --- /dev/null +++ b/prototype/Doubt/Hash.swift @@ -0,0 +1,78 @@ +public enum Hash: Hashable { + case Sequence([Hash]) + case String(Swift.String) + case Int(Swift.Int) + + public static func Case(label: Swift.String, _ hashes: Hash...) -> Hash { + return .Sequence([ .String(label) ] + hashes) + } + + public static func Case(index: Swift.Int, _ hashes: Hash...) -> Hash { + return .Sequence([ .Int(index) ] + hashes) + } + + public init(_ hashable: A) { + self = hashable.hash + } + + public init(_ hashable: A) { + self = .Int(hashable.hashValue) + } + + + public var hashValue: Swift.Int { + switch self { + case let .Sequence(s): + // Bob Jenkins’ one-at-a-time hash: https://en.wikipedia.org/wiki/Jenkins_hash_function + var hash = 0 + for each in s { + hash += each.hashValue + hash += hash << 10 + hash ^= hash >> 6 + } + hash += hash << 3 + hash ^= hash >> 11 + hash += hash << 15 + return hash + case let .String(s): + return s.hashValue + case let .Int(i): + return i.hashValue + } + } +} + +public func == (left: Hash, right: Hash) -> Bool { + switch (left, right) { + case let (.Sequence(a), .Sequence(b)): + return a == b + case let (.String(a), .String(b)): + return a == b + case let (.Int(a), .Int(b)): + return a == b + default: + return false + } +} + +public protocol AlgebraicHashable: Hashable { + var hash: Hash { get } +} + +extension AlgebraicHashable { + public var hashValue: Int { + return hash.hashValue + } +} + +extension RawRepresentable where RawValue: Hashable { + public var hash: Hash { + return Hash(rawValue) + } +} + +extension RawRepresentable where RawValue: AlgebraicHashable { + public var hash: Hash { + return Hash(rawValue) + } +} diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index 9ffff0cb0..8c83c3dee 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -1,4 +1,4 @@ -public enum Term: CustomDebugStringConvertible, CustomDocConvertible, CustomStringConvertible, Equatable { +public enum Term: CustomDebugStringConvertible, CustomDocConvertible, CustomStringConvertible, AlgebraicHashable { public init(_ out: Syntax) { self = .Roll(out) } @@ -27,6 +27,11 @@ public enum Term: CustomDebugStringConvertible, CustomDocConvertible, CustomStri } + public var hash: Hash { + return syntax.hash + } + + public static var Empty: Term { return Term(.Empty) } @@ -180,7 +185,8 @@ public enum Syntax: CustomDebugStringConvertible, CustomDocConvertible return ".Apply(\(f), [ \(s) ])" case let .Abstract(parameters, body): let s = parameters.map { String(reflecting: $0) }.joinWithSeparator(", ") - return ".Abstract([ \(s) ], \(body))" + let b = body.map { String(reflecting: $0) }.joinWithSeparator("\n") + return ".Abstract([ \(s) ], \(b))" case let .Assign(n, v): return ".Assign(\(n), \(v))" case let .Variable(n): @@ -215,3 +221,24 @@ public enum Syntax: CustomDebugStringConvertible, CustomDocConvertible } } } + +extension Syntax where Payload: AlgebraicHashable { + public var hash: Hash { + switch self { + case .Empty: + return .Case("Empty") + case let .Apply(f, vs): + return .Case("Apply", .Sequence([ f.hash ] + vs.map { $0.hash })) + case let .Abstract(parameters, body): + return .Case("Abstract", .Sequence(parameters.map { $0.hash }), .Sequence(body.map { $0.hash })) + case let .Assign(name, value): + return .Case("Assign", .String(name), value.hash) + case let .Variable(n): + return .Case("Variable", .String(n)) + case let .Literal(s): + return .Case("Literal", .String(s)) + case let .Group(n, vs): + return .Case("Group", n.hash, .Sequence(vs.map { $0.hash })) + } + } +}